Microsoft SQL父问题

时间:2010-12-18 16:12:39

标签: sql sql-server tsql

我很困难,我好好看看,但我不确定如何做到这一点。

我必须构建一个SP(TSQL)来恢复导航,但是我在订购导航时遇到了一些问题。

表示例

NavID     OrderID     ParentID      NavName
1         1           0             Home
2         2           0             About
3         3           0             Contact Us
4         1           2             About Us Page
5         2           2             About Us Page 2
6         1           4             Another SubPage

我需要带回的是上面的导航,以及下面的一个导航。

因此,如果我通过NavigationID 2,我希望结果会像这样返回

Home 
About
About Us Page 
About Us Page 2
Contact Us

如果我通过了NavigationID 6,我希望看到..

Home
About
About Us Page
Another SubPage
About Us Page 2
Contact Us

正如您所看到的那样,它会考虑OrderID,但请确保Child的顺序排在第一位。

我怎样才能做到这一点?

4 个答案:

答案 0 :(得分:2)

这是一个完整的脚本,可以满足您的需求(包括您的测试数据):

DECLARE @nav TABLE (
            NavID INT NOT NULL PRIMARY KEY,
            OrderID INT NOT NULL,
            ParentID INT,
            NavName nvarchar(MAX) NOT NULL 
        ); 
INSERT @nav 
        SELECT 1, 1, 0, 'Home' UNION ALL 
        SELECT 2, 2, 0, 'About' UNION ALL 
        SELECT 3, 3, 0, 'Contact Us' UNION ALL 
        SELECT 4, 1, 2, 'About Us Page' UNION ALL 
        SELECT 5, 2, 2, 'About Us Page 2' UNION ALL 
        SELECT 6, 1, 4, 'Another SubPage'; 

DECLARE @NavigationID int; 
SET     @NavigationID = 2;

WITH Ancestors AS (          
    SELECT @NavigationID NavID
    UNION ALL 
    SELECT  n.ParentID
    FROM    @nav n 
    JOIN Ancestors a ON (n.NavID = a.NavID)
),
VisibleNav AS (
    SELECT  n.*, CONVERT(FLOAT, 1)/SUM(1) OVER (PARTITION BY n.ParentID) Mul, ROW_NUMBER() OVER (PARTITION BY n.ParentID ORDER BY n.OrderID)-1 Pos
    FROM @nav n 
    JOIN Ancestors a ON n.ParentID = a.NavID
),
SortedNav AS (
    SELECT vn.*, vn.Pos*vn.Mul Sort, 1 Depth
    FROM VisibleNav vn
    WHERE vn.ParentID = 0
    UNION ALL
    SELECT vn.NavID, vn.OrderID, vn.ParentID, vn.NavName, vn.Mul*sn.Mul, vn.Pos, vn.Pos*(vn.Mul*sn.Mul)+sn.Sort, sn.Depth + 1
    FROM VisibleNav vn
    JOIN SortedNav sn ON sn.NavID = vn.ParentID
)
SELECT sn.NavID, sn.OrderID, sn.ParentID, sn.NavName
FROM SortedNav sn
ORDER BY sn.Sort, sn.Depth;

基本上,我有一个递归CTE来创建需要在导航中使用的所有父项的列表,包括父项的深度(以便顺序不依赖于ID),然后我加入导航那条目。

答案 1 :(得分:0)

公用表表达式允许递归查询。但是,它们可以非常慢并且可能会严重混淆查询优化器,特别是如果您使用(通常应该)参数化查询。对于不参与与其他(大)表的大型连接的小型表(例如导航),CTE的工作正常。您还可以将公用表表达式的结果缓存到表中,并在导航表发生更改时重新运行缓存查询(通常不常见,我猜) - 查询优化器更好地处理简单的多对多关系表

但是,还有其他方法可以在SQL Server中表示树。 你可以看看

最后,当我需要使用CTE时,我通常从......的开头开始。

with nav_tree (ParentID, ChildID, depth_delta) as ( 
    SELECT basenode.NavID, basenode.NavID, 0
    FROM NavTable AS basenode
  UNION ALL
    SELECT treenode.ParentID, basenode.NavID, depth_delta+1
    FROM NavTable AS basenode 
    JOIN nav_tree AS treenode ON basenode.ParentID=treenode.ChildID
)
--select statement here joining the nav_tree with the original table and whatnot

请注意,您的确切订单要求非常棘手;特别是你想要一个元素的子元素立即内联列出的地方;即你的第二个例子的“关于我们页面,另一个子页面,关于我们第2页”部分。特别是,这意味着您不能只按depth_delta订购,其次订购订单 - 您需要基于路径的排序。您可能希望在代码中而不是在sql中执行此操作,但可以在CTE中构建路径,如下所示:

with nav_tree (ParentID, ChildID, depth_delta,orderpath) as ( 
    SELECT basenode.NavID, basenode.NavID, 0, convert(varchar(MAX), basenode.OrderID)
    FROM NavTable AS basenode
  UNION ALL
    SELECT treenode.ParentID, basenode.NavID, depth_delta+1, treenode.orderpath+','+basenode.OrderID
    FROM NavTable AS basenode 
    JOIN nav_tree AS treenode ON basenode.ParentID=treenode.ChildID
)

...然后你可以order by那个顺序路径。由于你也希望每个祖先或自我展开“低于一级”,你仍然需要加入NavTable才能获得这些。

但是,考虑到您的排序要求,我建议使用HierarchyIDs :它们内置了您的排序语义,并且它们还避免了CTE可能暴露的潜在性能问题。

答案 2 :(得分:0)

我很想创建一个表变量,运行NavID< =父参数的查询,其中parentID = 0并添加到表中。然后获取子项,插入表变量,最后得到> NavID,其中ParentID = 0。

软糖,但应该有效。

DECLARE @Table TABLE (NavName nvarchar(50), OrderID int)
INSERT @Table (NavName, OrderID) (SELECT NavName, OrderID FROM @Table1 WHERE (ParentID = 0) AND (NavID <= @ParentID))
INSERT @Table (NavName,OrderID) (SELECT NavName, OrderID+ 500 FROM @Table1 WHERE ParentID = @ParentID )
INSERT @Table (NavName,OrderID) (SELECT NavName, OrderID + 1000 FROM @Table1 WHERE ParentID = 0 AND NavID > @ParentID)

SELECT * FROM @Table ORDER BY OrderID

答案 3 :(得分:0)

我有点不清楚您希望显示哪些节点...但如果我理解正确,这里的主要问题是如何订购结果节点。主要困难是排序标准是可变长度的:必须根据节点及其所有祖先的OrderId值的整个序列对节点进行排序。例如,节点6的排序顺序是'2,1,1'。

SQL不能很好地处理这样的可变长度序列。我建议我们使用NVARCHAR(MAX)值作为排序顺序。对于节点6,我们将使用'0002.0001.0001'。在这种形式中,可以使用字符串比较轻松地对节点进行排序。请注意,标识符值必须为零填充以确保正确排序(我任意选择填充到4位数 - 实际应用程序可能需要不同的选择)。

所以这就把我们带到了坚果和螺栓上。我们首先创建一个名为NavigationData的表来保存我们的测试数据:

SELECT NULL AS NavId, NULL AS OrderId, NULL AS ParentId, NULL AS NavName
  INTO NavigationData WHERE 1=0
UNION SELECT 1, 1, 0, 'Home'
UNION SELECT 2, 2, 0, 'About'
UNION SELECT 3, 3, 0, 'Contact Us'
UNION SELECT 4, 1, 2, 'About Us Page'
UNION SELECT 5, 2, 2, 'About Us Page 2'
UNION SELECT 6, 1, 4, 'Another SubPage'

现在,我们将创建一个帮助器视图,对于每个可能的所需节点,列出所有相关节点及其计算的路径字符串。正如我在开头所说,我觉得选择相关节点的标准是不明确的,因此可能需要针对具有比简单示例更多节点的数据集调整所需/相关的JOIN表达式。有了这个警告,这里是观点:

CREATE VIEW NavigationHierarchy AS
WITH
  hierarchy AS (
    SELECT
      NavId AS RootId
    , 1 AS Depth
    , NavId
    , RIGHT('0000' + CAST(OrderId AS NVARCHAR(MAX)), 4) AS Path
    , ParentId
    , NavName
    FROM NavigationData
    WHERE ParentId = 0
    UNION ALL
    SELECT
      parent.RootId
    , parent.Depth + 1 AS Depth
    , child.NavId
    , parent.Path + '.'
      + RIGHT('0000' + CAST(child.OrderId AS NVARCHAR(MAX)), 4) AS Path
    , child.ParentId
    , child.NavName
    FROM hierarchy AS parent
    INNER JOIN NavigationData AS child
      ON child.ParentId = parent.NavId
  )
SELECT
  desired.NavId AS DesiredNavId
, related.*
FROM hierarchy AS desired
INNER JOIN hierarchy AS related
  ON related.Depth <= desired.Depth + 1
  AND related.RootId IN (desired.RootId, related.RootId)

大多数查询是使用公共表表达式进行的层次结构的直接递归下降。解决方案的核心是生成Path列。当然,您可能更喜欢将此查询直接烘焙到更大的查询或存储过程中,而不是创建视图。但是,该视图便于测试。

有了视图,我们现在可以按要求的顺序生成所需的结果。为了便于说明,我在查询结果中包含了生成的路径。以下是节点2的查询:

SELECT NavName, Path
FROM NavigationHierarchy
WHERE DesiredNavId = 2
ORDER BY Path

得到以下特性:

Home              0001
About             0002
About Us Page     0002.0001
About Us Page 2   0002.0002
Contact Us        0003

和节点6:

SELECT NavName, Path
FROM NavigationHierarchy
WHERE DesiredNavId = 6
ORDER BY Path

得到以下特性:

Home              0001
About             0002
About Us Page     0002.0001
Another SubPage   0002.0001.0001
About Us Page 2   0002.0002
Contact Us        0003