我有一个包含父节点和子节点的表,每个节点都有一个订单号。
我正在尝试编写单个查询以按顺序输出它们,它用于包含类别和子类别的导航列表。
我可以在代码中而不是在SQL查询中管理它,但它涉及从查询循环中调用查询 - 我想避免这种情况。
表:
DBID | Title | ParentID | OrderNum
1 aaa 0 1
2 bbb 0 2
3 ccc 1 1
4 ddd 1 2
5 eee 2 1
我希望获得如下结果集:
DBID | Title | ParentID | OrderNum
1 aaa 0 1 <<< main
3 ccc 1 1 <<< child
4 ddd 1 2 <<< 2nd child
2 bbb 0 2 <<< main
5 eee 2 1 <<< child
我一直在考虑使用递归SQL选择或公用表表达式(CTE),但还没有弄明白。
任何人都可以帮我指出正确的方向吗?
(使用SQL Server 2005 / ASP.Net C#)
编辑: 我应该澄清一点,我只需要它到两个级别 - 即父级和小级,所以它不需要无限期地重新诅咒,所以我认为答案比我看到的要简单得多。
答案 0 :(得分:2)
是否需要递归?这似乎给出了正确的结果:
declare @t table
(DBID int
,Title varchar(10)
,ParentID int
,OrderNum int
)
insert @t
select 1,'aaa',0,1
union select 2,'bbb',0,2
union select 3,'ccc',1,1
union select 4,'ddd',1,2
union select 5,'eee',2,1
select *
from @t
order by ISNULL(NULLIF(ParentID,0),DBID)
,ParentID
,OrderNum
,DBID
修改强>
解释:
由于输入行与输出行对应1:1,而且只有行顺序发生变化,因此必须是排序问题。解决方案是识别每一行所属的父子组,然后按第一个排序,然后排序所有其他值。
执行此操作的唯一困难部分是父行(ParentID
= 0)从其DBID
隐式获取其组成员资格,而所有其他行从ParentID
获取它
第一个WHERE
条件(ISNULL(NULLIF(ParentID,0),DBID)
)处理此问题。它也可以写成
CASE WHEN ParentID = 0
THEN DBID
ELSE ParentID
END
我使用NULLIF...ISNULL
因为它更简洁,但如果排序条件更复杂,我会使用CASE...WHEN
。
答案 1 :(得分:1)
几年前,我发现最简单的方法(在打猎之后)是在表格中添加“深度”和“路径”列:
DBID | Title | ParentID | OrderNum | Path | Depth
0 Root null 1 / 0
1 aaa 0 1 /1/ 1
2 bbb 0 2 /2/ 1
3 ccc 1 1 /1/3/ 2
4 ddd 1 2 /1/4/ 2
5 eee 2 1 /2/5/ 2
然后我在表上有一对触发器,以便在插入和更新时自动更新这些触发器:
插入触发器
ALTER TRIGGER [dbo].[SiteMap_InsertTrigger]
ON [dbo].[SiteMap]
AFTER INSERT
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
UPDATE child
-- set the depth of this "child" to be the
-- depth of the parent, plus one.
SET Depth = ISNULL(parent.depth + 1, 0),
-- the lineage is simply the lineage of the parent,
-- plus the child's ID (and appropriate '/' characters
Path = ISNULL(parent.Path, '/') + LTrim(child.DBID) + '/'
-- we can't update the "inserted" table directly,
-- so we find the corresponding child in the
-- "real" table
FROM SiteMap child INNER JOIN inserted i ON i.Id = child.DBID
-- now, we attempt to find the parent of this
-- "child" - but it might not exist, so these
-- values may well be NULL
LEFT OUTER JOIN SiteMap parent ON child.ParentId = parent.DBID
END
更新触发器
ALTER TRIGGER [dbo].[SiteMap_UpdateTrigger]
ON [dbo].[SiteMap]
AFTER UPDATE
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- if we've modified the parentId, then we
-- need to do some calculations
IF UPDATE (ParentId)
BEGIN
UPDATE child
/*
to calculate the correct depth of a node, remember that
- old.depth is the depth of its old parent
- child.depth is the original depth of the node
we're looking at before a parent node moved.
note that this is not necessarily old.depth + 1,
as we are looking at all depths below the modified
node
The depth of the node relative to the old parent is
(child.depth - old.depth), then we simply add this to the
depth of the new parent, plus one.
*/
SET Depth = child.Depth - old.Depth + ISNULL(parent.Depth + 1,0),
Path = ISNULL(parent.Path,'/') + LTrim(old.DBID) + '/' +
right(child.Path, len(child.Path) - len(old.Path))
-- if the parentId has been changed for some row
-- in the "inserted" table, we need to update the
-- fields in all children of that node, and the
-- node itself
FROM SiteMap child
INNER JOIN inserted old ON child.Path LIKE old.Path + '%'
-- as with the insert trigger, attempt to find the parent
-- of the updated row
LEFT OUTER JOIN SiteMap parent ON old.ParentId=parent.DBID
END;
END;
通过搜索“路径”或“深度”,可以更快地跳转到特定类别并快速拉出所有子项和子项。
有趣的是,我刚刚发现SQL Server 2008包含一个新类型:HierarchyId,它基本上将Path列转换为紧凑类型。
答案 2 :(得分:0)
是的,您可以使用递归sql或cte。
来实现此目的我认为这个解决方案可以帮助您解决这个分层表选择。
http://geekswithblogs.net/dotNETPlayground/archive/2007/12/28/118017.aspx
答案 3 :(得分:0)
您可以使用nested-set model。这允许在一个SQL语句中提取整个树。缺点是写操作变得更复杂。
或者你可以写一个递归存储过程。
答案 4 :(得分:0)
这是一种递归方法。使用CTE。
with cte as ( select *, 1 as [level], [DBID] as [father] from @t where parentID=0 union all select t.*, [level]+1, cte.[father] from @t t inner join cte on cte.DBID=t.parentID ) select [DBID],Title, ParentID, OrderNum from cte order by [father], [level], OrderNum
答案 5 :(得分:0)
接受的答案是现场。这里也是linq:
var orderedItems = from item in dataContext.items
orderby (item.ParentID == 0 ? item.DBID : item.ParentID),
item.ParentID , item.OrderNum, item.DBID
select item;