Reputation: 1410
I have a table with an IDENTITY
column, and I have an INSTEAD OF
trigger on this table. If IDENTITY_INSERT
got turned on, I would want to insert the value being manually specified for the IDENTITY
column.
Is there any way to properly cope with this scenario, such as detecting the value of IDENTITY_INSERT
? From reading, it looks like detecting the current value of IDENTITY_INSERT
for a specific table has been impossible in the past. I don't know if it's possible with newer versions of SQL Server.
Table creation SQL:
CREATE TABLE [TestTable]
(
[Id] INTEGER IDENTITY NOT NULL PRIMARY KEY,
[ExampleField] BIT NOT NULL DEFAULT(1)
)
This is what I've currently tried, but it seems rather wasteful as IDENTITY_INSERT
is likely to be off for most of the time, meaning that it's always going to be failing on the first insert attempt, which seems wasteful, performance-wise.
CREATE TRIGGER [dbo].[trTestTable_ioi] ON [dbo].[TestTable] INSTEAD OF INSERT
AS
BEGIN
BEGIN TRY
INSERT INTO [TestTable]([Id],[ExampleField])
SELECT [Id], [ExampleField]
FROM [inserted]
END TRY
BEGIN CATCH
INSERT INTO [TestTable]([ExampleField])
SELECT [ExampleField]
FROM [inserted]
END CATCH
END
Upvotes: 3
Views: 2025
Reputation: 46203
If your IDENTITY
seed and increment is such that the generated value will always be non-zero (positive seed and increment or negative seed and increment), you can check for non-zero values in the virtual inserted
table and use that value when present. This relies on my observation that the identity value is zero with an INSTEAD OF
trigger and IDENTITY_INSERT OFF
. However, I could not find this behavior specifically documented so you should vet in your environment and use at your own risk.
Example:
CREATE TRIGGER [dbo].[trTestTable_ioi] ON [dbo].[TestTable] INSTEAD OF INSERT
AS
SET NOCOUNT ON;
IF EXISTS(SELECT * FROM inserted WHERE ID <> 0)
BEGIN
--IDENTITY_INSERT is ON
INSERT INTO [TestTable]([Id],[ExampleField])
SELECT [Id], [ExampleField]
FROM [inserted];
END
ELSE
BEGIN
--IDENTITY_INSERT is OFF
INSERT INTO [TestTable]([ExampleField])
SELECT [ExampleField]
FROM [inserted];
END;
GO
SET IDENTITY_INSERT dbo.TestTable OFF;
GO
--identity value auto-assigned
INSERT INTO TestTable VALUES(1);
GO
SET IDENTITY_INSERT dbo.TestTable ON;
GO
--explict identity value specified
INSERT INTO TestTable(ID, ExampleField) VALUES(11, 1);
GO
SET IDENTITY_INSERT dbo.TestTable OFF;
GO
--fails as expected because identity value cannot be specified with IDENTITY_INSERT OFF
INSERT INTO TestTable(ID, ExampleField) VALUES(11, 1);
GO
SET IDENTITY_INSERT dbo.TestTable ON;
GO
--fails as expected because identity value must be specified with IDENTITY_INSERT ON
INSERT INTO TestTable VALUES(1);
GO
Upvotes: 1
Reputation: 15816
The documentation for set identity_insert
states:
At any time, only one table in a session can have the IDENTITY_INSERT property set to ON. If a table already has this property set to ON, and a SET IDENTITY_INSERT ON statement is issued for another table, SQL Server returns an error message that states SET IDENTITY_INSERT is already ON and reports the table it is set ON for.
A terrible idea is to create a temporary table and try setting identity_insert on
for it. If there is an error, catch the message and extract the table name.
NB: Any time you are catching and parsing an error message you are fiddling with some fragile code. You should run, not walk, away now.
create table #Placeholder ( Id Int Identity );
begin try
set identity_insert #Placeholder on;
end try
begin catch
-- Sample error: IDENTITY_INSERT is already ON for table 'Scratch.dbo.Foo'. Cannot perform SET operation for table '#Placeholder'.
declare @Message as NVarChar(2048) = Error_Message();
print @Message;
declare @Prefix as NVarChar(2048) = 'IDENTITY_INSERT is already ON for table ''';
declare @Suffix as NVarChar(2048) = '''. Cannot perform SET operation for table ''#Placeholder''.';
declare @TableName as NVarChar(2048) = NULL;
if ( Left( @Message, Len( @Prefix ) ) = @Prefix and Right( @Message, Len( @Suffix ) ) = @Suffix )
set @TableName = Substring( @Message, Len( @Prefix ) + 1, Len( @Message ) - Len( @Prefix ) - Len( @Suffix ) );
else
print 'Unexpected error!';
print @TableName;
end catch
drop table #Placeholder;
Then there are the performance implications of creating/dropping a temporary table in a trigger.
Upvotes: 0