【问题标题】:Retrieve all Children and their Children, recursive SQL检索所有子项及其子项,递归 SQL
【发布时间】:2011-03-18 23:43:57
【问题描述】:

考虑数据库中的以下行:

Id      |   Parent
__________________
1           null
2           1
3           2
4           3
5           null
6           5

每个具有null ParentId 都是“所有者”/“超级父级”。

收集父母和他们的孩子的最佳方法是什么?我应该使用 LINQ 还是 存储过程

我希望最终结果是IEnumerable<IEnumerable<int>>

【问题讨论】:

  • 您的意思是拥有以自己为父母的物品吗?
  • 您是在暗示订单吗?如果你想要一个 IEnumerable 的所有孩子,你可以从表中选择 * 父不为空,所以你的问题肯定有更多......
  • 第2行的父级是第2行?哎哟。
  • @Eric,不,从来都不是这样,要么是null,要么是指向另一个“行”。
  • @Andomar,错过了,谢谢 :) @Kendrick,如您所见,它是一个 IEnumerableIEnumerable<int>,其中每个 IEnumerable<int> 代表父母之下的孩子。

标签: .net sql linq entity-framework linq-to-entities


【解决方案1】:

您也可以使用纯 SQL 解决方案;这是 SQL Server 的示例。为不同的数据库管理器重写它并不难:

/* Create table */
CREATE TABLE dbo.Nodes (ID int NOT NULL PRIMARY KEY, Parent int)

/* Insert sample data */
INSERT INTO Nodes VALUES (1,NULL)
INSERT INTO Nodes VALUES (2,1)
INSERT INTO Nodes VALUES (3,2)
INSERT INTO Nodes VALUES (4,3)
INSERT INTO Nodes VALUES (5,NULL)
INSERT INTO Nodes VALUES (6,5)

/* Create recursive function */
CREATE function dbo.fn_Root(@ID int) returns int
AS BEGIN
   DECLARE @R int

   SELECT @R = CASE WHEN Parent IS NULL THEN ID
                    ELSE dbo.fn_Root(Parent)
                 END
            FROM Nodes
           WHERE id = @id

   RETURN @R
END

/* Query the table */
SELECT ID, Parent, dbo.fn_Root(ID) AS Root
  FROM Nodes

/* Also, in SQL Server you can create a calculated column */
ALTER TABLE Nodes ADD Root AS dbo.fn_Root(id)

这是基本版本。但是如果你的数据有闭环(不是树形结构),这个会失败。为了防止代码进入死循环,可以这样改进函数:

CREATE function dbo.fn_Root(@ID int, @Initial int) returns int
AS BEGIN
   DECLARE @R int

   DECLARE @Parent int
   SELECT @Parent = Parent FROM Nodes WHERE ID = @ID

   IF @Parent IS NULL 
      SELECT @R = @ID   /* No parent, the root is the node itself */
   ELSE
      IF @Parent = @Initial 
         /* We have returned to initial node: endless loop. We return NULL to indicate no root exists */
         SELECT @R = NULL
      ELSE
         /* The root will be the root of the parent node */
         SELECT @R = dbo.fn_Root(@Parent,@Initial)   

   RETURN @R

END

/* Query the table */
SELECT ID, Parent, dbo.fn_Root(ID,ID) AS Root FROM Nodes

通过这个修改,如果函数返回NULL,则表明该节点是循环的一部分,因此它没有根节点。

【讨论】:

    【解决方案2】:

    数据库并不是真的要进行任意深度递归。这是在本地完成的所需操作。

    List<Item> items = context.Items.ToList();
    
    Dictionary<int, Item> itemsById = items.ToDictionary(item => item.Id);
    
    Dictionary<int, List<Item>> itemsByRoot = new Dictionary<int, List<Item>>();
    List<Item> cyclicals = new List<Item>();
    
    foreach(Item item in items)
    {
      HashSet<int> seenIt = new HashSet<int>();
      Item parent = item;
      while (parent.ParentId != null && !seenIt[parent.Id])
      {
        seenIt.Add(parent.Id);
        parent = itemsById[parent.ParentId];
      }
    
      if (parent.ParentId == null)
      {
        if (!itemsByRoot.ContainsKey(parent.Id))
        {
          itemsByRoot[parent.Id] = new List<Item>();
        }
        itemsByRoot[parent.Id].Add(item);
      }
      else
      {
        cyclicals.Add(item);
      }
    }
    

    【讨论】:

    • 这样做而不是接受的答案是否会带来任何性能优势?这感觉比它必须的要复杂得多。
    【解决方案3】:

    如果桌子不是太大,你最好的办法是通过这样做返回整个桌子 db.Categories

    一旦将整个类别表提取到实体框架中,EF 将使用关系跨度来修复对象图,因此当您执行 category.SubCategories 时,您将获得所有子类别。 这样做的好处是,你的 sql 不会很复杂,因为它基本上是 select * from categories。 EF 将完成大部分艰苦的工作来修复对象图,以使所有子对象都与其父对象正确对齐。

    您也可以使用其他人提到的关于使用公用表表达式的内容。

    我在书中介绍了两个这样的概念。

    5-11 使用关系跨度 5-2 加载完整的对象图(CTE)

    【讨论】:

      【解决方案4】:

      在 SQL 中,您可以使用 CTE 进行查询。例如,要检索包含其父节点和树中最高父节点的节点列表:

      declare @t table (id int, parent int)
      insert @t (id, parent) values (1, null), (2,1), (3,2), (4,3), (5,null), (6,5)
      
      ; with cte as (
          select  id, parent, id as head
          from    @t
          where   parent is null
          union all
          select  child.id, child.parent, parent.head
          from    @t child
          join    cte parent
          on      parent.id = child.parent
      )
      select  *
      from    cte
      

      这给出了:

      id  parent  head
      1   NULL    1
      2   1       1
      3   2       1
      4   3       1
      5   NULL    5
      6   5       5
      

      请注意,我更改了您的示例数据,因此第 2 行不再是其自身的子代,而是第 1 行的子代。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2013-01-19
        • 2014-02-11
        • 1970-01-01
        • 1970-01-01
        • 2016-02-04
        • 1970-01-01
        • 2017-05-10
        相关资源
        最近更新 更多