简体   繁体   中英

insertion in a self referenced table

If I have a table

Table
{
ID int primary key identity,
ParentID int not null foreign key references Table(ID)
}

how does one insert first row into a table?

From a business logic point of view not null constraint on ParentID should not be dropped.

In SQL Server, a simple INSERT will do:

create table dbo.Foo
(
ID int primary key identity,
ParentID int not null foreign key references foo(ID)
)
go

insert dbo.Foo (parentId) values (1)

select * from dbo.Foo

results in

    ID          ParentID
----------- -----------
    1           1

If you're trying to insert a value that will be different from your identity seed, the insertion will fail.

UPDATE:

The question is not too clear on what the context is (ie is the code supposed to work in a live production system or just a DB setup script) and from the comments it seems hard-coding the ID might not be an option. While the code above should normally work fine in the DB initialization scripts where the hierarchy root ID might need to be known and constant, in case of a forest (several roots with IDs not known in advance) the following should work as intended:

create table dbo.Foo
(
ID int primary key identity,
ParentID int not null foreign key references foo(ID)
)
go

insert dbo.Foo (parentId) values (IDENT_CURRENT('dbo.Foo'))

Then one could query the last identity as usual ( SCOPE_IDENTITY , etc.). To address @usr's concerns, the code is in fact transactionally safe as the following example demonstrates:

insert dbo.Foo (parentId) values (IDENT_CURRENT('dbo.Foo'))
insert dbo.Foo (parentId) values (IDENT_CURRENT('dbo.Foo'))
insert dbo.Foo (parentId) values (IDENT_CURRENT('dbo.Foo'))

select * from dbo.Foo

select IDENT_CURRENT('dbo.Foo')
begin transaction   
    insert dbo.Foo (parentId) values (IDENT_CURRENT('dbo.Foo'))
    rollback

select IDENT_CURRENT('dbo.Foo')

insert dbo.Foo (parentId) values (IDENT_CURRENT('dbo.Foo'))

select * from dbo.Foo

The result:

ID          ParentID
----------- -----------
1           1
2           2
3           3

currentIdentity
---------------------------------------
3

currentIdentity
---------------------------------------
4

ID          ParentID
----------- -----------
1           1
2           2
3           3
5           5

If you need to use an explicit value for the first ID, when you insert your first record, you can disable the checking of the IDENTITY value (see: MSDN: SET IDENTITY_INSERT (Transact-SQL) ).

Here's an example that illistrates this:

CREATE TABLE MyTable
(
  ID int PRIMARY KEY IDENTITY(1, 1),
  ParentID int NOT NULL,
  CONSTRAINT MyTable_ID FOREIGN KEY (ParentID) REFERENCES MyTable(ID)
);

SET IDENTITY_INSERT MyTable ON;
INSERT INTO MyTable (ID, ParentID)
VALUES (1, 1);
SET IDENTITY_INSERT MyTable OFF;

WHILE @@IDENTITY <= 5
BEGIN
    INSERT INTO MyTable (ParentID)
    VALUES (@@IDENTITY);
END;

SELECT *
  FROM MyTable;

IF OBJECT_ID('MyTable') IS NOT NULL
    DROP TABLE MyTable;

It seems like the NOT NULL constraint is not true for the root node in the tree. It simply does not have a parent. So the assumption that ParentID is NOT NULL is broken from the beginning.

I suggest you make it nullable and add an index on ParentID to validate that there is only one with value NULL :

create unique nonclustered index ... on T (ParentID) where (ParentID IS NULL)

It is hard to enforce a sound tree structure in SQL Server. You can get multiple roots for example or cycles in the graph. It is hard to validate all that and it is unclear if it is worth the effort. It might well be, depending on the specific case.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM