SQL:按特定顺序从自引用表中删除数据

时间:2011-03-04 23:39:33

标签: sql-server-2005 tsql

我有一张这样的表:

groupId guid PK
parentId guid
name
left int
right int

从parentId到groupId有一个外键(这是一个自引用表)。

左侧和右侧是MPTT左/右值,用于维护层次结构。这里要注意的重要事项是左值越大,项目嵌套越深(换句话说:对于任何给定的项目,其左值总是大于所有父项的左值)。


我正在尝试编写一个快速DELETE语句来删除除最顶层组(总是有一个空的GUID值)之外的所有内容,例如:

DELETE FROM [group] WHERE [groupId] <> '00000000-0000-0000-0000-000000000000'`

不幸的是,这不起作用,因为在任何给定组下面通常存在防止删除的子组。如果反复运行DELETE查询,最终会删除所有内容,但这显然不是一个好的解决方案。

我想要的是相当于:

DELETE FROM [group] WHERE [groupId] <> '00000000-0000-0000-0000-000000000000' 
ORDER BY [left] DESC

当然,这是不允许的语法,但实际上,它应首先删除具有最大左值的项目,以确保下面没有任何组可防止因FK约束而删除。

我也尝试过:

delete from [group] where groupid in (
  select top 1000000 * from [group] 
  where groupid <> '00000000-0000-0000-0000-000000000000' 
  ORDER BY [left] desc
)

这是有效的语法(如果你也使用TOP,你只能使用ORDER BY)但实际上并没有导致DELETE按返回的行的顺序发生,所以它仍然不起作用。

是否可以这样做,而无需借助光标逐个删除行?

5 个答案:

答案 0 :(得分:5)

了解您正在使用的SQL Server版本会很有趣,因为在SQL Server 2005和2008中,查询计划显示在完成参照完整性检查之前完成了针对聚簇索引的所有删除操作。约束检查 延迟!

删除操作符后,删除的groupId值由Eager Spool捕获。然后,计划根目录下的序列运算符将播放第二个分支,该分支从线轴读取所有行,并使用半连接检查引用已删除行的其他行。计划中的最后一个操作是Assert,如果参照完整性受到损害,将引发错误。

复制脚本,使用原始问题中引用的MPTT文章中显示的数据:


USE     tempdb
;
IF      OBJECT_ID(N'dbo.Test', N'U')
        IS NOT NULL
        DROP TABLE dbo.Test
;
CREATE  TABLE dbo.Test
        (
        groupId     UNIQUEIDENTIFIER NOT NULL PRIMARY KEY NONCLUSTERED,
        parentId    UNIQUEIDENTIFIER REFERENCES dbo.Test(groupId) NULL,
        name        NVARCHAR(50) NOT NULL,
        lft         INTEGER NOT NULL,
        rgt         INTEGER NOT NULL,
        )
;
CREATE  NONCLUSTERED INDEX nc1 ON dbo.Test (parentId)
;
INSERT  dbo.Test (groupId, parentId, name, lft, rgt)
VALUES  ({guid '00000000-0000-0000-0000-000000000000'}, NULL, N'Food', 1, 18),
        ({guid '99163693-B535-47A1-9F5B-E5F235CEB8E2'}, {guid '00000000-0000-0000-0000-000000000000'}, N'Fruit', 2, 11),
        ({guid '6A3FFED1-0230-4909-A7D4-DD9CBA674A1D'}, {guid '99163693-B535-47A1-9F5B-E5F235CEB8E2'}, N'Red', 3, 6),
        ({guid '2E7A25D8-8555-4FEC-A0D3-8B8E60501F6A'}, {guid '6A3FFED1-0230-4909-A7D4-DD9CBA674A1D'}, N'Cherry', 4, 5),
        ({guid '20613684-1081-4A42-AEAE-F21250D56703'}, {guid '99163693-B535-47A1-9F5B-E5F235CEB8E2'}, N'Yellow', 7, 10),
        ({guid 'B6D5A4AB-1A10-4069-842D-AAE09E73B3CD'}, {guid '20613684-1081-4A42-AEAE-F21250D56703'}, N'Banana', 8, 9),
        ({guid '174FF0C3-BAE0-4023-8819-EC89E080834C'}, {guid '00000000-0000-0000-0000-000000000000'}, N'Meat', 12, 17),
        ({guid '3CA5356E-343B-461C-8602-B9ECAEA1304D'}, {guid '174FF0C3-BAE0-4023-8819-EC89E080834C'}, N'Beef', 13, 14),
        ({guid '79664130-ACDF-48CE-A33D-7473153819E8'}, {guid '174FF0C3-BAE0-4023-8819-EC89E080834C'}, N'Pork', 15, 16)
;
DELETE  dbo.Test
WHERE   groupId > {guid '00000000-0000-0000-0000-000000000000'}
;

答案 1 :(得分:2)

尝试

ALTER TABLE [group] NOCHECK CONSTRAINT ALL
go
DELETE FROM [group] WHERE [groupId] <> '00000000-0000-0000-0000-000000000000'
go
ALTER TABLE [group] CHECK CONSTRAINT ALL
go

如果表格很大,这可能是个不错的选择。

select * into #temptable from [group] WHERE [groupId] = '00000000-0000-0000-0000-000000000000'
go
truncate table [group]
go
select * into [group] from #temptable
go
drop table #temptable
go

答案 2 :(得分:0)

你可以删除FK,删除,然后在完成后放回FK吗?有点感觉像作弊,但它可能会完成工作。

答案 3 :(得分:0)

您可以尝试禁用约束检查:

CHECK / NOCHECK CONSTRAINT

答案 4 :(得分:0)

你可以写:

declare @idgroup guid;
set @idgroup = 'whatever';

WITH n AS
(
    SELECT groupId, parentId
        FROM Table WHERE groupId=@idgroup
    UNION ALL
    SELECT e.groupId, e.parentId
        FROM Table e JOIN n ON n.groupId=e.parentId
)
--Unlike some other systems, we don’t have to worry about the order the records are deleted, 
--since all SQL Server referential constraints are deferred till the end of the query.
DELETE FROM Table
WHERE groupId IN (SELECT groupId FROM n)

这将删除特定组及其所有后代,而无需担心FK约束和订单

还有一篇关于参照完整性和级联操作的非常好的文章here

相关问题