简体   繁体   English

SQL Server - 如何管理表中的分层数据?

[英]SQL Server - How to manage hierarchical data in a table?

I use SQL Server 2000. 我使用SQL Server 2000。

Suppose I have two tables like the following: 假设我有两个表,如下所示:

Area
----------------------------------
ID| Name   | HierarchyLevel
----------------------------------
1 | World  |     1
2 | America|     2
3 | Europe |     2
4 | Africa |     2
5 | USA    |     3

and

AreaHierarchy
------------------------
ID | ParentID | ChildID
------------------------
 1 |   1      |    2
 2 |   1      |    3
 3 |   1      |    4
 4 |   2      |    5

where 哪里

AreaHierarchy.ParentID and AreaHierarchy.ChildID are FKs of Area.ID AreaHierarchy.ParentID和AreaHierarchy.ChildID是Area.ID的FK

How can I find the nth parent of USA? 我怎样才能找到美国的第n位父母?

Is it possible without looping? 没有循环可能吗?

Probably not. 可能不是。

No loops, no recursion 没有循环,没有递归

The best thing is to add additional field in your second table, that would be called ie. 最好的办法是在第二个表中添加额外的字段,即调用ie。 Parents and would simply store parent IDs in a string like: Parents ,只需将父ID存储在一个字符串中,如:

AreaHierarchy
------------------------------------
ID | ParentID | ChildID | Parents
------------------------------------
 1 |    1     |    2    | 1/
 2 |    1     |    3    | 1/
 3 |    1     |    4    | 1/
 4 |    2     |    5    | 1/2/

This way you can easily get to any parent in the branch without recursion or any other complicated procedure. 这样,您可以轻松访问分支中的任何父级,而无需递归或任何其他复杂的过程。 The cost in processing is very small you just copy parent's Parents value and add one more ID. 处理成本非常小,您只需复制父级的“ Parents值并再添加一个ID。 And since you probably need to read more than write/update, this is the best solution to your problem. 而且,由于您可能需要阅读更多而不是写入/更新,因此这是解决您问题的最佳方案。

And if I were you, I'd just keep one table for the data you have. 如果我是你,我会为你拥有的数据保留一张表。 Join both tables into one. 将两个表合并为一个。 Level could also be computed based on counting slashes in Parents varchar value but I wouldn't recommend doing that. 也可以根据Parents varchar值中的计数斜线计算级别,但我不建议这样做。

Additional 'catch' you should be aware of 您应该注意的额外“捕获”

If your data is mostly reads/writes and much less updates, this structure is really performant. 如果您的数据主要是读/写和更少的更新,这种结构确实非常高效。 But if your table does a lot more updates than read/writes, you should avoid this technique. 但是,如果您的表执行的更新比读/写更多,则应避免使用此技术。 Why? 为什么? Imagine you have a very deep tree with lots of children. 想象一下,你有一棵很深的树,有很多孩子。 Changing a parent of some node high up in near the root would mean you should update Parents of the whole subtree nodes. 在根目录附近将某个节点的父节点更改为高位意味着您应该更新整个子树节点的Parents节点。

Should work 应该管用

CREATE PROCEDURE find_nth_parent 
    @id INT,
    @level INT
AS
BEGIN
    SET NOCOUNT ON;

    DECLARE @counter INT
    SET @counter = 1

    DECLARE @currentItem INT
    DECLARE @currentItemNew INT

    SET @currentItem = @id

    WHILE @counter <= @level
    BEGIN
        SET @currentItemNew = NULL
        SELECT @currentItemNew = ParentID FROM AreaHierarchy WHERE ChildId = @currentItem
        IF @currentItemNew IS NULL
        BEGIN
            SELECT NULL
            RETURN 
        END
        SET @currentItem = @currentItemNew
        SET @counter = @counter + 1
    END
    SELECT @currentItem
END

Calling 调用

EXEC find_nth_parent 5,2

returns 1 which means "World" (2nd parent), calling 返回1表示“世界”(第二个父母),呼叫

EXEC find_nth_parent 5,1

return 2, which means "America" (1st parent). 返回2,表示“美国”(第一父母)。

Hope it helps 希望能帮助到你

You could use recursion. 你可以使用递归。 If you have SQL Server 2005 or newer you can use Common Table Expressions. 如果您有SQL Server 2005或更高版本,则可以使用公用表表达式。 If not you realistically need to use User Defined Functions. 如果不是,您实际上需要使用用户定义的函数。


An example of a UDF to do that could be... UDF的一个例子可能是......

CREATE FUNCTION get_nth_parent(area_id AS INT, n as INT)
RETURNS INT
AS

IF (n = 0) RETURN area_id

DECLARE @return INT
SELECT
   @return = dbo.get_nth_parent(AreaHierarchy.ParentID, n-1)
FROM
   AreaHierarchy
WHERE
   ChildID = area_id

RETURN @return


An example using Common Table Experessions could be... 使用Common Table Experessions的示例可能是......

DECLARE @hierarchy TABLE (
   parent_id  INT,
   child_id   INT
)
INSERT INTO @hierarchy SELECT 1,2
INSERT INTO @hierarchy SELECT 1,3
INSERT INTO @hierarchy SELECT 1,4
INSERT INTO @hierarchy SELECT 2,5


;WITH
   relative_distance (
      child_id,
      parent_id,
      distance
   )
AS
(
   SELECT
      child_id,
      parent_id,
      1
   FROM
      @hierarchy

   UNION ALL

   SELECT
      [relative_distance].child_id,
      [hierarchy].parent_id,
      [relative_distance].distance + 1
   FROM
      [relative_distance]
   INNER JOIN
      @hierarchy AS [hierarchy]
         ON [hierarchy].child_id = [relative_distance].parent_id
)

SELECT
   parent_id
FROM
   [relative_distance]
WHERE
   child_id = 5
   AND distance = 2

In SQL Server 2005+, you'd use a CTE in a function: 在SQL Server 2005+中,您将在函数中使用CTE:

create function get_parent(@child as int, @parent_level as int)
returns int
as
begin
    declare @parent int

    ;with parentage as (
         select 
             h.parent_id, 
             h.child_id,
             0 as level
         from 
             areahierarchy h
         where
             h.child_id = @child
         union all
         select
             h.parent_id,
             h.child_id,
             p.level + 1 as level
         from
             areahierarchy h
             inner join parentage p on
                 h.parent_id = p.child_id
         where
             p.level < @parent_level
    )

    select @parent = p.child_id from parentage p 
    where level = (select max(level) from parentage)

    return @parent
end

我知道您希望支持SQL Server 2000,但我认为应该注意SQL Server 2008 Hierarchy ID函数GetAncestor()完全符合您的要求。

You can use the nested set model by Joe Celko https://en.wikipedia.org/wiki/Nested_set_model 您可以使用Joe Celko的嵌套集模型https://en.wikipedia.org/wiki/Nested_set_model

or even better The closure Table model 甚至更好的闭包表模型

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM