Recently, my colleague worked with some Dynamic SQL to insert some data in one table. What he tried in that code is inserting some data from one to another table, with the fact he need to preserve all IDs as they are in source table. For that purpose, he tried to use IDENTITY_INSERT attribute to allow inserting specific values in primary key column (IDENTITY column). When he tried to execute code, error informed him that is not allowed inserting values in identity column, though he write code for allowing such action.
Example of what he tried is following:
-- ======================================== -- -- Create table [dbo].[Test] -- ======================================== -- CREATE TABLE [dbo].[Test] ( [ID] INT IDENTITY(1, 1) PRIMARY KEY, [Name] VARCHAR(50) NOT NULL ) -- ======================================== -- -- Insert data in table using Dynamic SQL -- ======================================== -- DECLARE @sqlOn NVARCHAR(MAX), @sqlMain NVARCHAR(MAX), @sqlOff NVARCHAR(MAX) SET @sqlOn = N'SET IDENTITY_INSERT [dbo].[Test] ON;' SET @SqlMain = N'INSERT INTO [dbo].[Test] ([ID], [Name]) VALUES(1, ''Test'')' SET @sqlOff = N'SET IDENTITY_INSERT [dbo].[Test] OFF;' EXEC sys.sp_executesql @sqlOn EXEC sys.sp_executesql @sqlMain EXEC sys.sp_executesql @sqlOff
The error he got was:
Msg 544, Level 16, State 1, Line 1
Cannot insert explicit value for identity column in table 'Test' when IDENTITY_INSERT is set to OFF.
At first glance, everything was OK, if you write code without Dynamic SQL, it will execute without any problems, but if you put it in separate variables to simulate three different Dynamic SQL codes, error raises.
The problem lays in fact that SQL treats every EXEC statement as a different scope, because they are different stored procedures execution, and what is executed in first EXEC statement, is not visible to second and so on.
To resolve this problem, we need to put all code in one variable and execute as single statement. Example is shown below:
DECLARE @sqlMain NVARCHAR(MAX) SET @SqlMain = N'SET IDENTITY_INSERT [dbo].[Test] ON; INSERT INTO [dbo].[Test] ([ID], [Name]) VALUES(1, ''Test'') SET IDENTITY_INSERT [dbo].[Test] OFF;' EXEC sys.sp_executesql @sqlMain
The conclusion is that we cannot have IDENTITY_INSERT property separated from remaining code, because SQL Server treat it like separate scopes.