2012-11-10 65 views
7

如果我有一個表插入一個自我引用的表

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

一個人如何插入第一行到表?

從業務邏輯的角度來看,不應該刪除ParentID上的null約束。

+1

Intersting問題。是否有可能引用自己?如果它是新創建的,你甚至可以創建1條記錄? – YvesR

+2

你的第一排的父母是哪一行? –

+2

對於第一行ParentID應該是ID。如果我想在我的樹結構中使用多個根,那麼任何根都有ParentID == ID。因此,問題可以擴展爲 - 如何插入根行?存儲過程可以使用SCOPE_IDENTITY來進行插入嗎? – Nezreli

回答

5

在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 

如果你想插入會從你的身份種子,插入不同的值將失敗。

UPDATE:

的問題不是上下文是什麼太明確的(即是應該在實時生產系統,或只是一個DB安裝腳本工作的代碼),並從意見似乎很難編碼該ID可能不是一個選項。雖然上面的代碼通常可以在數據庫初始化腳本中正常工作,但可能需要知道層次結構根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_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 
+1

這依賴於第一個ID是1,這是不能保證的。失敗的插入(超時)可能會破壞第一個ID值,從而導致表永久中斷。 – usr

+0

@usr依賴於在問題中使用的默認種子1的身份。在兩個地方替換種子,它仍然有效。這也是在答案中提到的。然而,我對這種情況的細節非常感興趣,在這種情況下,表格會失效,請您詳細說明一下。 –

+0

在表格中插入一行,回滾事務。這會浪費一個身份值。由於併發原因,身份值永遠不會回滾。 – usr

1

如果你需要使用一個明確的數值爲第一ID時,您插入第一條記錄,則可以禁用IDENTITY值的檢查(請參閱:MSDN: SET IDENTITY_INSERT (Transact-SQL))。

下面是illistrates這樣一個例子:

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; 
2

這似乎是NOT NULL約束是不適合的樹的根節點如此。它根本沒有父母。所以假設ParentIDNOT NULL從一開始就被打破了。

我建議你把它可空和ParentID添加一個索引來驗證有隻有一個值NULL

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

這是很難執行SQL Server中的聲音樹形結構。您可以在圖表中獲得多個根或循環。很難驗證所有這一切,並且不清楚是否值得努力。根據具體情況,這可能是好的。

+0

當然,需要使用SQL Server 2008+才能使用篩選索引... –

+0

有趣的解決方案,但客戶在SQL Server 2005上。 – Nezreli

+0

@Nezreli我可以推薦使用觸發器。觸發器可以查看插入的行來基本驗證任何內容。它可能看起來像這樣:'IF(SELECT COUNT(*)FROM T WHERE ParentID IS NULL)> 1 RAISEERROR' – usr

相關問題