[7.x] Improve SQL Server last insert id retrieval #33430
Merged
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
This PR addresses issue #32883
It adds the method
SqlServerGrammar@compileInsertGetId
, and if it gets merged this method will changean
INSERT
statement from this:Into this:
line breaks added for readability
There are other features in SQL Server that allows retrieving the last inserted ID, but from my research,
for a
INSERT
statement that inserts a single row,SCOPE_IDENTITY
seems to be the more reliable.The alternative methods are:
OUTPUT
clause: can yield wrong results when target table has anINSTEAD OF INSERT
trigger.reference: https://docs.microsoft.com/en-us/sql/t-sql/queries/output-clause-transact-sql
IDENT_CURRENT()
function: not transaction safe.reference: https://docs.microsoft.com/en-us/sql/t-sql/functions/ident-current-transact-sql
@@IDENTITY
system function: not trigger safe.reference: https://docs.microsoft.com/en-us/sql/t-sql/functions/identity-transact-sql
I first tried using the
OUTPUT
clause as from my previous knowledge was a recommended solutionfrom Microsoft due to some bugs with
SCOPE_IDENTITY
and@@IDENTITY
reference: https://support.microsoft.com/en-us/help/2019779/you-may-receive-incorrect-values-when-using-scope-identity-and-identit
But according to the same reference above the bug was fixed in SQL Server 2005, which is not supported anymore
neither by Microsoft nor by Laravel.
Also using the
OUTPUT
clause incurred in using a temporaryTABLE
variable to address triggers usage.And as noted on the list above the
OUTPUT
clause can yield incorrect results if the target tablehas an
INSTEAD OF INSERT
trigger.So the most reliable way seems to use
SCOPE_IDENTITY()
.Note that I added
SCOPE_IDENTITY()
in the same SQL generated bycompileInsertGetId
, so it is guaranteed to berun in the same session as the
INSERT
statement.This is necessary as per docs (and my local testing),
SCOPE_IDENTITY()
is safe on thesame scope (e.g. triggers won't affect it) and session (connection, transaction, etc).
reference: https://docs.microsoft.com/en-us/sql/t-sql/functions/scope-identity-transact-sql
I also removed the ODBC part from the
SqlServerProcessor
as it run the query on separate database call.In my local testing, when the target table of a
INSERT
statement has a trigger which inserts a rowon another table, using
SCOPE_IDENTITY
on separate database call returns an incorrect result(the auto generated id from the row inserted by trigger).
How this PR differs from previous attempts
I found two other previous attempts to solve this:
PR [8.x] SqlServer schema fixes #32957
This PR ended having a very similar code to that PR (difference being the column name used in the
SqlServerGrammar@compileInsertGetId
)As mentioned above, I first tried using the
OUTPUT
clause, as from my previous knowledge seemed to bea better solution, but after some research on MSDN and other sources I ended up using
SCOPE_IDENTITY()
asPR [8.x] SqlServer schema fixes #32957.
Difference is this PR only addressees the last insert id reported on issue Create method on model returning wrong primary key value (SQLSRV) #32883, whereas PR [8.x] SqlServer schema fixes #32957 addresses
other SQL Server related issues.
I would close this PR in favor of PR [8.x] SqlServer schema fixes #32957 if that is preferred, as it is more feature complete.
Branch
sqlsrv-insert-id
from @staudenmeir forkIssue Create method on model returning wrong primary key value (SQLSRV) #32883 OP (@kadevland) mentions that fork. The only difference is that this PR does not fallback
to
@@IDENTITY
, as perSCOPE_IDENTITY()
docs (linked above):Which is not the case as the
SELECT SCOPE_IDENTITY()
is added in the same SQL code as theINSERT
statement.Also as noted above
@@IDENTITY
can return the wrong value if the target table of theINSERT
statement hasan associated trigger.
Also @staudenmeir did not make a PR from his fork. I consider him to have more knowledge regarding databases and
Laravel's Eloquent/Query Builder than me. If he knows any issues that prevented him to make a PR from his fork -
maybe he is researching or know a better solution to this problem - I would bet on his attempt and close this PR.
Implementation notes
SqlServerProcessor
The changed code in the
SqlServerProcessor
class was copied from the same method in thePostgresProcessor
class.Also as already mentioned above, I removed the ODBC special handling to avoid executing
SCOPE_IDENTITY()
in a separate query.
SET NOCOUNT ON
set nocount on
is needed so SQL Server returns theSELECT
results instead of the affected rows count.Testing
I updated an existing test and added a new one to test the code changed by this PR.
Real usage testing
I setup a local application with SQL Server 2017 running in a docker instance (I run Linux) and did some local
tests with sample application code. SQL Server version 2017 was used because I already had this docker image installed
for some other projects I need it.
Everything worked as expected. I tested both inserts in a table with no triggers and in a table with a
INSERT
trigger.If someone has any suggestions or spot any errors please let me know and I'll make any improvements needed.