My application inserts some data into a table.
insert into this_Table (id, value, value)
I have then created a trigger that does a simple insert, into a different table with a primary key.
insert into temp_wc_triggertest values ('test', GETDATE())
My problem is then, the application tries to look for scope_identity, from the first insert. However, it gets overwritten by the trigger, which changes the scope identity to the primary key of temp_wc_triggertest.
How can I stop the trigger overwriting scope_identity?
I realise there is not much code to help here, which would normally be classed as a bad question, but I don't have permission to access to the full application code at the moment, so I am hoping this is answerable as is.
This is on SQL Server 2008 R2
EDIT: I have looked at the code, and it does use scope_identity
Your client is most certainly using ##IDENTITY instead of SCOPY_IDENTITY()
Here is a SQL Fiddle with some code you can test on.
SQL Fiddle
MS SQL Server 2008 Schema Setup:
create table T1(ID int identity(1,1));
create table T2(ID int identity(1000, 1));
go
create trigger tr_T1 on T1 for insert
as
insert into T2 default values;
Query:
insert into T1 default values
select ##identity as "##identity",
scope_identity() as "scope_identity()"
Results:
| ##IDENTITY | SCOPE_IDENTITY() |
---------------------------------
| 1000 | 1 |
If you are using SCOPE_IDENTIY correctly, you may also be experiencing a known bug - http://connect.microsoft.com/SQLServer/feedback/details/328811
MS has fixed it permanently for 2012, and has patches available for 2008 and 2008R2.
The reason for it overwriting scope identity is still not clear, it could possibly be related to the bug mentioned. However a fix was found:
A temporary table was created "temp_wc"
Then at the end of the trigger, identity insert was switched on for that table, and an insert was done, for the ID that we want to keep after the trigger has fired. This method can be thought of as overwriting the overwritten scope identity again.
SET IDENTITY_INSERT ON
INSERT INTO temp_wc VALUES (#ID, 'fix scope identity error')
This may help anyone in the future with this issue:
https://learn.microsoft.com/en-us/sql/t-sql/functions/scope-identity-transact-sql?view=sql-server-2017
You may need to patch SQL Server, as it looks like the SCOPE_IDENTITY should return the inserted ID on the table that is actually receiving the primary insert, not the SQL trigger insert statements.
From the Microsoft docs for SQL Server 2017 (in case link is broken):
Remarks SCOPE_IDENTITY, IDENT_CURRENT, and ##IDENTITY are similar
functions because they return values that are inserted into identity
columns.
IDENT_CURRENT is not limited by scope and session; it is limited to a
specified table. IDENT_CURRENT returns the value generated for a
specific table in any session and any scope. For more information, see
IDENT_CURRENT (Transact-SQL).
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.
For example, there are two tables, T1 and T2, and an INSERT trigger is
defined on T1. When a row is inserted to T1, the trigger fires and
inserts a row in T2. This scenario illustrates two scopes: the insert
on T1, and the insert on T2 by the trigger.
Assuming that both T1 and T2 have identity columns, ##IDENTITY and
SCOPE_IDENTITY return different values at the end of an INSERT
statement on T1. ##IDENTITY returns the last identity column value
inserted across any scope in the current session. This is the value
inserted in T2. SCOPE_IDENTITY() returns the IDENTITY value inserted
in T1. This was the last insert that occurred in the same scope. The
SCOPE_IDENTITY() function returns the null value if the function is
invoked before any INSERT statements into an identity column occur in
the scope.
Failed statements and transactions can change the current identity for
a table and create gaps in the identity column values. The identity
value is never rolled back even though the transaction that tried to
insert the value into the table is not committed. For example, if an
INSERT statement fails because of an IGNORE_DUP_KEY violation, the
current identity value for the table is still incremented.
Another link here:
http://www.sqlbadpractices.com/how-not-to-retrieve-identity-value/
The problem with this code is that you may not retrieve the identity
value that you inserted. For example, if there is a trigger on the
table performing an insert on another table, you will get the last
created identity value. Even if you never create any trigger, you may
get skewed results with replicated tables since SQL Server creates his
own replication triggers.
Use SELECT IDENT_CURRENT(‘tablename’)
"It returns the last IDENTITY value produced in a table, regardless of the connection that created the value, and regardless of the scope of the statement that produced the value."
See this link for details.
http://blog.sqlauthority.com/2007/03/25/sql-server-identity-vs-scope_identity-vs-ident_current-retrieve-last-inserted-identity-of-record/
Related
I was reading this answer about different methods of getting the last identity value entered into a database.
From what I understand, ##IDENTITY is usually a very bad idea because it might return an identity that is not the one you expected--for example an identity value that was recently created by a trigger.
But what if your code is in a transaction?
For example this is a simplified version of a transaction I'm doing (using ColdFusion):
<cftransaction>
<cfquery name="queryInsertA" datasource="source">
INSERT INTO tableA (columnName) VALUES (value)
</cfquery>
<cfquery name="queryInsertB" datasource="source">
INSERT INTO tableB (fkey_tableA, columnName) VALUES (##IDENTITY, value)
</cfquery>
</cftransaction>
Since, "If a transaction is successful, all of the data modifications made during the transaction are committed and become a permanent part of the database," does this mean that it would also prevent the isses that can arise when using ##IDENTITY? Or am I misunderstanding the behavior of transactions?
The answer you linked already explains what the main issue is with ##IDENTITY: scope. If your insert triggers another insert, you get an unexpected identity back. Transactions do not change anything.
If you wanted to get the last identity value inserted to a table, use the Ident_current() function.
Select ident_current ('your table name')
Also you can use scope_identity(), It will bring the identity value of a tablein that particular scope only.
Select scope_identity()
You don't need ##Identity, nor do you need 2 separate queries. Use the Scope_identity() function for integrity and make it a part of the same connection & query - like so.
<cfquery name="putUser" datasource="#dsn#">
SET NOCOUNT ON
INSERT INTO users(username, email)
VALUES
('#usersname#','#email#' )
SELECT SCOPE_IDENTITY() AS newId FROM users
SET NOCOUNT OFF
</cfquery>
<cfoutput>#putUser.newID#</cfoutput>
This will be totally safe, but like all db transactions it will still be subject to deadlocks so tuning is still important.
CFTRANSACTION is good for multiple DB operations where some CF logic might also be involved, but let the DB locking and transactional system work for you by keeping it together.
You can also use the result attribute of cfquery. If the query performs an INSERT of an identity or auto-increment value for ID, there will be a key named GENERATEDKEY returned in the structure.
<cftransaction>
<cfquery name="queryInsertA" datasource="source" result="resultA">
INSERT INTO tableA (columnName) VALUES (value)
</cfquery>
<cfquery name="queryInsertB" datasource="source">
INSERT INTO tableB (fkey_tableA, columnName) VALUES (#resultA.generatedKey#, value)
</cfquery>
</cftransaction>
Keep in mind this is only CF9 and higher.
You can use Sequence and use that during insert as below:
CREATE SEQUENCE Testseq
START WITH 1
INCREMENT BY 1 ;
Access the sequence by using below query:
SELECT NEXT VALUE FOR Testseq;
To make it simple :
IF
You know you'r ALL ALONE in the db system, this means, no other user, or process running at the same time, no other transaction running, there is absolutly ZERO activity at the time you use it, and i mean it, ZERO ACTIVITY, then, ok...
ELSE
NO ! If anything like i listed above does occur exactly while your transact is running, you will end up with the wrong identity.
It depends on what else is running at the same time as your transaction is instantiated. If there is a trigger on a table unrelated to the transaction that can insert a new identity value, the transaction scope you are currently in will not protect you.
For example say I create a SPROC that updates Table_A and inserts a record into it. This table has an identity field on it that will increment the ID value in that table each time a new record is inserted. Inside my SPROC I create a transaction and place my insert inside the transaction. After the insert I store the value of ##IDENTITY in a variable inside the same transaction.
Now I also have another table Table_B with it's own identity value but this table is trigger maintained. If I am executing my SPROC to insert a row in Table_A and during this update Table_B is also updated via a trigger, it is possible that when I retrieve the value of ##IDENTITY, it will actually give me the value of the ID created for Table_B rather than Table_A.
You should definitely use Transactions in your stored procedures but you are better off selecting the MAX(ID) of the table you inserted into to retrieve the ID you created rather than ##IDENTITY.
I have an existing table that has an identity column and a column that has a constraint to ensure that it matches the value in the identity column.
(I know that this is not a good design, but it is an old table that can not be changed at the moment)
I know that I can use the IDENT_CURRENT('mytable') as a value during the insert to put the newly created identity value into the other column (FakeID).
INSERT INTO MyTable (FakeID)
SELECT IDENT_CURRENT('MyTable')
I have seen that this has issues when inserting multiple records, but for this use I am only inserting a single record.
My question is how safe is this to use?
Mainly, is there a risk from inserts made under different sessions or in a different scope?
Also, does this behave as expected when the table is truncated?
Any other suggestions are welcome.
SCOPE_IDENTITY() is going to be a safer bet here. If someone inserts into the table before this part of your transaction runs, IDENT_CURRENT will grab the newly generated Id from the other transaction.
So I would declare a variable and set its value to SCOPE_IDENTITY(), something like this
DECLARE #Id INT
SET #Id = SCOPE_IDENTITY()
INSERT INTO MyTable (FakeID)
SELECT #Id
Here are some posts on a similar issue:
http://bytes.com/topic/sql-server/answers/471026-ident_current-problem
IDENT_CURRENT equivalent for current session
CREATE TABLE Table1 :
Id int IDENTITY(1,1),
PK_Column1 nvarchar(50) Primary Key.
INSERT INTO Table1 (PK_Column1) VALUES ('Name'+Id)
Result:
Id PK_Column1
1 Name1
Is this possible? Or do I need to manage the Id column myself for this to work?
From the documentation:
After an INSERT, SELECT INTO, or bulk copy statement completes, ##IDENTITY contains the last identity value generated by the statement.
This applies to all the other identity checkers.
You should probably write a little SP to update the record immediately after your insert if this is what you need. Given that your primary_key appears to be some unusual composite of the ID and a varchar, you would also be best reviewing your data model.
It's important to note the difference with ##IDENTITY and SCOPE_IDENTITY():
##IDENTITY and SCOPE_IDENTITY 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.
I'm trying to get a the key-value back after an INSERT-statement.
Example:
I've got a table with the attributes name and id. id is a generated value.
INSERT INTO table (name) VALUES('bob');
Now I want to get the id back in the same step. How is this done?
We're using Microsoft SQL Server 2008.
No need for a separate SELECT...
INSERT INTO table (name)
OUTPUT Inserted.ID
VALUES('bob');
This works for non-IDENTITY columns (such as GUIDs) too
Use SCOPE_IDENTITY() to get the new ID value
INSERT INTO table (name) VALUES('bob');
SELECT SCOPE_IDENTITY()
http://msdn.microsoft.com/en-us/library/ms190315.aspx
INSERT INTO files (title) VALUES ('whatever');
SELECT * FROM files WHERE id = SCOPE_IDENTITY();
Is the safest bet since there is a known issue with OUTPUT Clause conflict on tables with triggers. Makes this quite unreliable as even if your table doesn't currently have any triggers - someone adding one down the line will break your application. Time Bomb sort of behaviour.
See msdn article for deeper explanation:
http://blogs.msdn.com/b/sqlprogrammability/archive/2008/07/11/update-with-output-clause-triggers-and-sqlmoreresults.aspx
Entity Framework performs something similar to gbn's answer:
DECLARE #generated_keys table([Id] uniqueidentifier)
INSERT INTO Customers(FirstName)
OUTPUT inserted.CustomerID INTO #generated_keys
VALUES('bob');
SELECT t.[CustomerID]
FROM #generated_keys AS g
JOIN dbo.Customers AS t
ON g.Id = t.CustomerID
WHERE ##ROWCOUNT > 0
The output results are stored in a temporary table variable, and then selected back to the client. Have to be aware of the gotcha:
inserts can generate more than one row, so the variable can hold more than one row, so you can be returned more than one ID
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.
SQL Server 2008 or newer only. If it's 2005 then you're out of luck.
There are many ways to exit after insert
When you insert data into a table, you can use the OUTPUT clause to
return a copy of the data that’s been inserted into the table. The
OUTPUT clause takes two basic forms: OUTPUT and OUTPUT INTO. Use the
OUTPUT form if you want to return the data to the calling application.
Use the OUTPUT INTO form if you want to return the data to a table or
a table variable.
DECLARE #MyTableVar TABLE (id INT,NAME NVARCHAR(50));
INSERT INTO tableName
(
NAME,....
)OUTPUT INSERTED.id,INSERTED.Name INTO #MyTableVar
VALUES
(
'test',...
)
IDENT_CURRENT: It returns the last identity created for a particular table or view in any session.
SELECT IDENT_CURRENT('tableName') AS [IDENT_CURRENT]
SCOPE_IDENTITY: It returns the last identity from a same session and the same scope. A scope is a stored procedure/trigger etc.
SELECT SCOPE_IDENTITY() AS [SCOPE_IDENTITY];
##IDENTITY: It returns the last identity from the same session.
SELECT ##IDENTITY AS [##IDENTITY];
##IDENTITY Is a system function that returns the last-inserted identity value.
There are multiple ways to get the last inserted ID after insert command.
##IDENTITY : It returns the last Identity value generated on a Connection in current session, regardless of Table and the scope of statement that produced the value
SCOPE_IDENTITY(): It returns the last identity value generated by the insert statement in the current scope in the current connection regardless of the table.
IDENT_CURRENT(‘TABLENAME’) : It returns the last identity value generated on the specified table regardless of Any connection, session or scope. IDENT_CURRENT is not limited by scope and session; it is limited to a specified table.
Now it seems more difficult to decide which one will be exact match for my requirement.
I mostly prefer SCOPE_IDENTITY().
If you use select SCOPE_IDENTITY() along with TableName in insert statement, you will get the exact result as per your expectation.
Source : CodoBee
The best and most sure solution is using SCOPE_IDENTITY().
Just you have to get the scope identity after every insert and save it in a variable because you can call two insert in the same scope.
ident_current and ##identity may be they work but they are not safe scope. You can have issues in a big application
declare #duplicataId int
select #duplicataId = (SELECT SCOPE_IDENTITY())
More detail is here Microsoft docs
You can use scope_identity() to select the ID of the row you just inserted into a variable then just select whatever columns you want from that table where the id = the identity you got from scope_identity()
See here for the MSDN info http://msdn.microsoft.com/en-us/library/ms190315.aspx
Recommend to use SCOPE_IDENTITY() to get the new ID value, But NOT use "OUTPUT Inserted.ID"
If the insert statement throw exception, I except it throw it directly. But "OUTPUT Inserted.ID" will return 0, which maybe not as expected.
This is how I use OUTPUT INSERTED, when inserting to a table that uses ID as identity column in SQL Server:
'myConn is the ADO connection, RS a recordset and ID an integer
Set RS=myConn.Execute("INSERT INTO M2_VOTELIST(PRODUCER_ID,TITLE,TIMEU) OUTPUT INSERTED.ID VALUES ('Gator','Test',GETDATE())")
ID=RS(0)
You can append a select statement to your insert statement.
Integer myInt =
Insert into table1 (FName) values('Fred'); Select Scope_Identity();
This will return a value of the identity when executed scaler.
* Parameter order in the connection string is sometimes important. * The Provider parameter's location can break the recordset cursor after adding a row. We saw this behavior with the SQLOLEDB provider.
After a row is added, the row fields are not available, UNLESS the Provider is specified as the first parameter in the connection string. When the provider is anywhere in the connection string except as the first parameter, the newly inserted row fields are not available. When we moved the the Provider to the first parameter, the row fields magically appeared.
After doing an insert into a table with an identity column, you can reference ##IDENTITY to get the value:
http://msdn.microsoft.com/en-us/library/aa933167%28v=sql.80%29.aspx
How can I get the ##IDENTITY for a specific table?
I have been doing
select * from myTable
as I assume this sets the scope, from the same window SQL query window in SSMS I then run
select ##IDENTITY as identt
It returns identt as null which is not expected since myTable has many entrie in it already..
I expect it to return the next available ID integer.
myTable has a ID column set to Primary key and auto increment.
You can use IDENT_CURRENT
IDENT_CURRENT( 'table_name' )
Note that IDENT_CURRENT returns the last identity value for the table in any session and any scope. This means, that if another identity value was inserted after your identity value then you will not retrieve the identity value that you inserted.
You can only truly use SELECT ##IDENTITY after an insert - the last insert into a table that has an IDENTITY column is the value you'll get back.
You cannot "limit" it to a table - the value in ##IDENTITY - and by the way, I'd strongly recommend using SCOPE_IDENTITY() instead!! - is the last value on any IDENTITY column that was set.
The problem with ##IDENTITY is that it will report back the last IDENTITY value inserted into any table - if your INSERT into your data table will cause e.g. a trigger to write an entry into an Audit table and that Audit table has an IDENTITY field, you'll get back that IDENTITY value - not the one inserted into your table. SCOPE_IDENTITY() solves that.
IDENT_CURRENT does what you want. But don't.
This is in addition to marc_s' answer
I've never known ##IDENTITY to be used this way, i've only ever used it to access the ID of a newly inserted record.
That's correct. ##IDENTITY cannot be used the way you think it can be. It can only be used after an INSERT into a table. Let's consider this for a scenario:
You have two tables: Order (Primary Key: OrderID), OrderDetails (Foreign Key: OrderID)
You perform
INSERT INTO Order
VALUES('Pillows')
-- Note that OrderId is not mentioned in Values since it is auto number (primary key)
Now you want to perform insert into OrderDetail. But you don't always remember how many records there were in Order table prior to you having inserted the record for 'Pillows' and hence you don't remember what was the last PrimaryKey inserted into Order table. You could but even then you wouldn't want to specifically mention to insert (let's say OrderID of 1) when you insert into OrderDetail table.
Hence, your OderDetail insert would work kinda like so:
INSERT INTO OrderDetail
VALUES (##IDENTITY,'Soft Pillows')
Hope this explains the user of ##IDENTITY.