删除 SQL 表中的分层数据
Posted
技术标签:
【中文标题】删除 SQL 表中的分层数据【英文标题】:Deleting hierarchical data in SQL table 【发布时间】:2009-05-19 12:08:38 【问题描述】:我有一个包含分层数据的表。 一列“ParentId”,其中包含其父项的 Id(“ID” - 键列)。
删除一行时,我想删除所有子项(所有级别的嵌套)。
怎么做?
谢谢
【问题讨论】:
【参考方案1】:在 SQL Server 上:使用递归查询。给定 CREATE TABLE tmp(Id int, Parent int),使用
WITH x(Id) AS (
SELECT @Id
UNION ALL
SELECT tmp.Id
FROM tmp
JOIN x ON tmp.Parent = x.Id
)
DELETE tmp
FROM x
JOIN tmp ON tmp.Id = x.Id
【讨论】:
【参考方案2】:添加外键约束。以下示例适用于 mysql (syntax reference):
ALTER TABLE yourTable
ADD CONSTRAINT makeUpAConstraintName
FOREIGN KEY (ParentID) REFERENCES yourTable (ID)
ON DELETE CASCADE;
这将在数据库级别进行操作,dbms 将确保一旦删除了一行,所有引用的行也将被删除。
【讨论】:
SQL Server 2005 不支持自引用级联删除。当您尝试删除包含“子”行的行时会出错。 作者在我写这个答案的时候还没有指定一个 DBMS。我将其留作参考。 嗯,这很公平。感谢您的澄清。 ON DELETE CASCADE 也适用于 Oracle。这是我认为最好的答案(如果您的 DBMS 支持的话)。 澄清一下,我只在主从关系为一级深度并在两个表中建模的情况下使用此方法。【参考方案3】:当行数不太大时,erikkallen 的递归方法有效。
这是使用临时表收集所有子表的替代方法:
create table #nodes (id int primary key)
insert into #nodes (id) values (@delete_id)
while @@rowcount > 0
insert into #nodes
select distinct child.id
from table child
inner join #nodes parent on child.parentid = parent.id
where child.id not in (select id from #nodes)
delete
from table
where id in (select id from #nodes)
它从带有@delete_id 的行开始,然后从那里下降。 where 语句是为了防止递归;如果你确定没有,你可以忽略它。
【讨论】:
我的sql没那么强。所以我问这个:你为什么需要:"select id from table where id = @delete_id" 为什么不能只使用@delete_id 作为值? 如果这比递归 CTE 表现更好,我会感到非常惊讶【参考方案4】:取决于您存储层次结构的方式。如果您只有 ParentID,那么它可能不是您采取的最有效的方法。为了便于子树操作,您应该有一个额外的列Parents
,它将存储所有父 ID,例如:
/1/20/25/40
这样您就可以通过以下方式获取所有子节点:
where Parents like @NodeParents + '%'
第二种方法
除了 ParentID,您还可以有 left
和 right
值。这样做的插入速度较慢,但选择操作非常快。尤其是在处理子树节点时...http://en.wikipedia.org/wiki/Tree_traversal
第三种方法 如果您使用 SQL 2005+,请检查递归 CTE
第四种方法 如果您使用 SQL 2008,请检查 HierarchyID 类型。它为您的情况提供了足够的可能性。 http://msdn.microsoft.com/en-us/magazine/cc794278.aspx
【讨论】:
不,我不想将整个父母链存储在一个列中,因为涉及到不断变化的父母。并且很难跟踪所有这些。不能像现在这样做吗? 对 Hierarchy 数据的主要操作是什么?是插入、更新还是读取? 我倾向于同意第一种方法 - 我们有分层数据表,我们正在做同样的事情。它有助于摆脱孩子,如果您需要对树进行基于路径的处理(例如必须快速返回父母的所有孩子进行计算),它也会有所帮助。我们最初尝试使用触发器来维护这一点,但确实发现添加大量数据时对性能的影响令人望而却步。 如果您使用的是 SQL Server 2005,那么您肯定应该研究第三种方法。这样,您就不必维护具有多个属性的父母列,这很麻烦。【参考方案5】:像这样向表中添加触发器
在 myTable 上创建触发器 TD_MyTable 以删除为 -- 删除一级子级 delete M from deleted D inner join myTable M 在 D.ID = M.ID
每次删除都会在同一张表上调用一次删除,重复调用触发器。在线查看书籍以了解其他规则。触发器可以嵌套的次数可能有限制。
ST
【讨论】:
SQL SERVER 2005 Express 中可用的那些触发器? 我相信它们是,但是你必须自己写出来。它没有向导。 我猜触发器会起作用,但触发器的问题是它会在每次删除时激活,即使在我只想删除一行的情况下......【参考方案6】:取决于您的数据库。如果您使用的是 Oracle,您可以这样做:
DELETE FROM Table WHERE ID IN (
SELECT ID FROM Table
START WITH ID = id_to_delete
CONNECT BY PRIOR.ID = ParentID
)
预计到达时间:
如果没有 CONNECT BY,它会变得有点棘手。正如其他人所建议的那样,触发器或级联删除约束可能是最简单的。
【讨论】:
我使用的是 MS SQL SERVER 2005 express【参考方案7】:触发器只能用于深度不超过 32 层的层次结构:
http://sqlblog.com/blogs/alexander_kuznetsov/archive/2009/05/11/defensive-database-programming-fun-with-triggers.aspx
【讨论】:
是的。最大级别深度约为 8,9。无论如何,我不认为我会使用触发器,因为我不需要在数据库上运行的每个删除命令上激活触发器。【参考方案8】:你想要的是这些表之间的referential integrity。
【讨论】:
以上是关于删除 SQL 表中的分层数据的主要内容,如果未能解决你的问题,请参考以下文章