Sql 在自引用表中插入

Sql 在自引用表中插入,sql,sql-server,Sql,Sql Server,如果我有桌子 Table { ID int primary key identity, ParentID int not null foreign key references Table(ID) } 如何将第一行插入表中 从业务逻辑的角度来看,不应删除ParentID上的NOTNULL约束。在SQL Server中,简单的插入即可: create table dbo.Foo ( ID int primary key identity, ParentID int not null foreig

如果我有桌子

Table
{
ID int primary key identity,
ParentID int not null foreign key references Table(ID)
}
如何将第一行插入表中


从业务逻辑的角度来看,不应删除ParentID上的NOTNULL约束。

在SQL Server中,简单的插入即可:

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
导致

    ID          ParentID
----------- -----------
    1           1
如果尝试插入与标识种子不同的值,则插入将失败

更新:

问题不太清楚上下文是什么(即,代码应该在实时生产系统中工作还是仅在DB设置脚本中工作),从评论中可以看出,对ID进行硬编码可能不是一个选项。虽然上面的代码在DB初始化脚本中正常工作,其中可能需要知道层次结构根ID并保持不变,但在林(几个根的ID事先未知)的情况下,以下代码应按预期工作:

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'))
然后可以像往常一样查询最后一个标识(
SCOPE\u identity
,等等)。为了解决@usr的问题,该代码实际上是事务安全的,如下例所示:

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
结果是:

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

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

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

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

如果需要为第一个ID使用显式值,则在插入第一条记录时,可以禁用对标识值的检查(请参见:)

这里有一个例子说明了这一点:

创建表MyTable
(
ID int主键标识(1,1),
ParentID int不为空,
约束MyTable_ID外键(ParentID)引用MyTable(ID)
);
设置IDENTITY_INSERT MyTable ON;
插入MyTable(ID,ParentID)
数值(1,1);
将IDENTITY_INSERT MyTable设置为OFF;

而@IDENTITY对于树中的根节点,
notnull
约束似乎不是真的。它根本没有父对象。因此,
ParentID
notnull
的假设从一开始就被打破了

我建议您将其设置为NULL,并在
ParentID
上添加一个索引,以验证只有一个值为
NULL

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

在SQL Server中很难强制实施健全的树结构。例如,可以在图中获得多个根或循环。很难证实所有这些,也不清楚这是否值得努力。这很可能是,取决于具体情况。

有趣的问题。甚至有可能提到你自己吗?如果是新创建的,您如何创建1条记录?第一行的父行是哪一行?对于第一行,ParentID应该是ID。如果我想在树结构中使用多个根,那么任何根都有ParentID==ID。因此,问题可以展开为-如何插入根行?存储过程可以使用SCOPE_IDENTITY进行此插入吗?@Nezreli我已经更新了我的答案,以满足您的要求。有一件事我不能完全确定,2005年的表现是否与我目前正在使用的2008R2有所不同。如果您能证实这一点,我们将不胜感激。@Nezreli您解决了这个问题吗?结果是什么?这取决于第一个ID为1,但不能保证。插入失败(超时)可能会破坏第一个ID值,导致表永久损坏。@usr依赖于与问题中使用的默认种子1一起使用的标识。在两个地方更换种子,它仍然有效。答案中也提到了这一点。然而,我对这个场景的细节很感兴趣,在这个场景中,这将导致表损坏,请您详细说明一下?在表中插入一行,回滚事务。这将浪费一个标识值。标识值不会因为并发原因而回滚。@usr在这种情况下,表不会被“破坏”,您只需要有一个不同的当前标识,可以通过多种方式进行处理。是的,它需要查询当前标识值。这很尴尬,在交易上也不一致。无法自动查询值并插入行。我认为,几乎任何依赖于下一个标识值的精确值的代码都从根本上被破坏了。可能会有例外情况,但由于我所述的原因,这种情况不属于例外情况。当然,需要在SQL Server 2008+上才能使用筛选索引…有趣的解决方案,但客户使用的是SQL Server 2005。@Nezreli我可以推荐使用触发器。触发器可以查看插入的行以基本上验证任何内容。它可能是这样的:
IF(从T中选择COUNT(*),其中ParentID为NULL)>1 RAISEERROR