This blog post demonstrates various approaches when using native compilation to insert rows into parent/child tables.
First, let’s create tables named Parent and Child, and relate them with a FOREIGN KEY constraint. Note that the Parent table uses the IDENTITY property for the PRIMARY KEY column.
DROP TABLE IF EXISTS dbo.Child GO DROP TABLE IF EXISTS dbo.Parent GO CREATE TABLE dbo.Parent ( ParentID INT IDENTITY PRIMARY KEY NONCLUSTERED ,Name CHAR(50) NOT NULL ,Description CHAR(100) NOT NULL ) WITH (MEMORY_OPTIMIZED = ON, DURABILITY = SCHEMA_AND_DATA) GO CREATE TABLE dbo.Child ( ChildID INT IDENTITY PRIMARY KEY NONCLUSTERED ,ParentID INT NOT NULL FOREIGN KEY REFERENCES dbo.Parent (ParentID) INDEX IX_Child_ParentID ,Name CHAR(50) NOT NULL ,Description CHAR(100) NOT NULL ) WITH (MEMORY_OPTIMIZED = ON, DURABILITY = SCHEMA_AND_DATA) GO
Next, we attempt to create a natively compiled procedure that performs an INSERT to the Parent table, and tries to reference the key value we just inserted, with @@IDENTITY.
Scenario 1
CREATE OR ALTER PROCEDURE dbo.Proc_InsertParentAndChild WITH NATIVE_COMPILATION, SCHEMABINDING AS BEGIN ATOMIC WITH (TRANSACTION ISOLATION LEVEL = SNAPSHOT, LANGUAGE = N'English') INSERT dbo.Parent ( Name ,Description ) VALUES ( 'Parent1' ,'SomeDescription' ) DECLARE @NewParentID INT SELECT @NewParentID = SCOPE_IDENTITY() INSERT dbo.Child ( ParentID ,Name ,Description ) VALUES ( @NewParentID ,'Child1' ,'SomeDescription' ) END GO EXEC dbo.Proc_InsertParentAndChild SELECT * FROM Parent ORDER BY ParentID SELECT * FROM Child ORDER BY ParentID GO
This works, but there are other approaches to solving this problem.
Next, we’ll try to DECLARE a table variable, and OUTPUT the new key value.
Scenario 2
CREATE OR ALTER PROCEDURE dbo.Proc_InsertParentAndChild WITH NATIVE_COMPILATION, SCHEMABINDING AS BEGIN ATOMIC WITH (TRANSACTION ISOLATION LEVEL = SNAPSHOT, LANGUAGE = N'English') DECLARE @NewParentID TABLE (ParentID INT NOT NULL) INSERT dbo.Parent ( Name ,Description ) OUTPUT Inserted.ParentID INTO @NewParentID /* Msg 12305, Level 16, State 24, Procedure Proc_InsertParentAndChild, Line 7 [Batch Start Line 64] Inline table variables are not supported with natively compiled modules. */ VALUES ( 'Parent1' ,'SomeDescription' ) END GO
But again we have issues with unsupported T-SQL.
Now we’ll try creating a memory-optimized table variable outside the native procedure, and then declare a variable of that type inside the native procedure.
Scenario 3
CREATE TYPE dbo.ID_Table AS TABLE ( ParentID INT NOT NULL PRIMARY KEY NONCLUSTERED ) WITH (MEMORY_OPTIMIZED = ON) GO CREATE OR ALTER PROCEDURE dbo.Proc_InsertParentAndChild WITH NATIVE_COMPILATION, SCHEMABINDING AS BEGIN ATOMIC WITH (TRANSACTION ISOLATION LEVEL = SNAPSHOT, LANGUAGE = N'English') DECLARE @NewParentID dbo.ID_Table INSERT dbo.Parent ( Name ,Description ) OUTPUT Inserted.ParentID INTO @NewParentID VALUES ( 'Parent1' ,'SomeDescription' ) DECLARE @NewParentValue INT = (SELECT ParentID FROM @NewParentID) INSERT dbo.Child ( ParentID ,Name ,Description ) VALUES ( @NewParentValue ,'Child1' ,'SomeDescriptioin' ) END GO
This compiles, so now let’s test it.
EXEC dbo.Proc_InsertParentAndChild SELECT * FROM Parent ORDER BY ParentID SELECT * FROM Child ORDER BY ParentID GO
This works great, but for completeness, we should test other possibilities.
This time, we’ll recreate the tables, but we’ll leave off the IDENTITY property for the Parent table. Instead of IDENTITY, we’ll create a SEQUENCE, and attempt to generate the next value within the native module.
Scenario 4
DROP PROCEDURE IF EXISTS dbo.Proc_InsertParentAndChild go DROP TABLE IF EXISTS dbo.Child GO DROP TABLE IF EXISTS dbo.Parent GO CREATE TABLE dbo.Parent ( ParentID INT PRIMARY KEY NONCLUSTERED – no IDENTITY property used here! ,Name CHAR(50) NOT NULL ,Description CHAR(100) NOT NULL ) WITH (MEMORY_OPTIMIZED = ON, DURABILITY = SCHEMA_AND_DATA) GO CREATE TABLE dbo.Child ( ChildID INT IDENTITY PRIMARY KEY NONCLUSTERED ,ParentID INT NOT NULL FOREIGN KEY REFERENCES dbo.Parent (ParentID) INDEX IX_Child_ParentID ,Name CHAR(50) NOT NULL ,Description CHAR(100) NOT NULL ) WITH (MEMORY_OPTIMIZED = ON, DURABILITY = SCHEMA_AND_DATA) GO CREATE SEQUENCE dbo.ParentSequence AS INT GO CREATE OR ALTER PROCEDURE dbo.Proc_InsertParentAndChild WITH NATIVE_COMPILATION, SCHEMABINDING AS BEGIN ATOMIC WITH (TRANSACTION ISOLATION LEVEL = SNAPSHOT, LANGUAGE = N'English') DECLARE @NextParentSequence INT = NEXT VALUE FOR dbo.ParentSequence INSERT dbo.Parent ( ParentID ,Name ,Description ) VALUES ( @NextParentSequence ,'Parent1' ,'SomeDescription' ) INSERT dbo.Child ( ParentID ,Name ,Description ) VALUES ( @NextParentSequence ,'Child1' ,'SomeDescriptioin' ) END GO /* Msg 10794, Level 16, State 72, Procedure Proc_InsertParentAndChild, Line 19 [Batch Start Line 176] The operator 'NEXT VALUE FOR' is not supported with natively compiled modules. */
But this fails, because as the error states, we can’t use NEXT VALUE FOR within native modules.
Scenario 5
How about if we generate the next value for the sequence outside the module, and pass that value?
Let’s see —
CREATE OR ALTER PROCEDURE dbo.Proc_InsertParentAndChild ( @NewParentValue INT ) WITH NATIVE_COMPILATION, SCHEMABINDING AS BEGIN ATOMIC WITH (TRANSACTION ISOLATION LEVEL = SNAPSHOT, LANGUAGE = N'English') INSERT dbo.Parent ( ParentID ,Name ,Description ) VALUES ( @NewParentValue ,'Parent1' -- Name - char(50) ,'SomeDescription' -- Description - char(100) ) INSERT dbo.Child ( ParentID ,Name ,Description ) VALUES ( @NewParentValue ,'Child1' ,'SomeDescriptioin' ) END GO SELECT * FROM Parent ORDER BY ParentID SELECT * FROM Child ORDER BY ParentID DECLARE @NextParentSequence INT SELECT @NextParentSequence = NEXT VALUE FOR dbo.ParentSequence EXEC dbo.Proc_InsertParentAndChild @NextParentSequence SELECT * FROM Parent ORDER BY ParentID SELECT * FROM Child ORDER BY ParentID GO
This also works, so we’ll add it to our arsenal. But there’s one weird thing – the value that was inserted into the Parent table is –2147483647, which is probably not what we intended. So we’ll have to tidy up our SEQUENCE a bit.
DROP SEQUENCE dbo.ParentSequence GO CREATE SEQUENCE dbo.ParentSequence AS INT START WITH 1 GO DECLARE @NextParentSequence INT SELECT @NextParentSequence = NEXT VALUE FOR dbo.ParentSequence EXEC dbo.Proc_InsertParentAndChild @NextParentSequence SELECT * FROM Parent ORDER BY ParentID SELECT * FROM Child ORDER BY ParentID
Everything looks good now:
In this post, we have verified three different ways to successfully insert into parent/child records, when using native compilation.