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.
Hi, Ned. Nice post. Instead of using @@IDENTITY, you can use SCOPE_IDENTITY() to get the previously-inserted identity value. This is a better alternative even for non-memory-optimized tables, as it gets rid of the risk of triggers firing and getting the latest identity value of some unexpected table that the trigger inserted into.
Pingback: Parent-Child Relationships And Native Compilation – Curated SQL
Hi Kevin,
Very much appreciate your feedback – I’ve updated the post to use SCOPE_IDENTITY.
This article bothers me. Since I spent so many decades of my life working on SQL in ANSI/ISO standards, I want to get the terminology and the concepts right. The words “parent” and “child” come from network databases; the relationship is unidirectional. In RDBMS we have “referencing” and “referenced” tables; it has no direction. Furthermore, the referenced and referencing tables can be exactly the same table, while child – parent cannot be.
You also used identity as a key; by definition, a count of physical insertion attempts in one table on one machine, one time cannot be a logical key. You also have violations of ISO 11179 naming rules, but that’s nota big deal for sample schemas.
Otherwise, the article was pretty good and interesting. I am just a picky, pendatic old fart 🙂