【问题标题】:SQL To Get Parent From Self Referencing TableSQL 从自引用表中获取父级
【发布时间】:2010-09-26 01:48:56
【问题描述】:

我有一张这样的桌子:

Item
{
   int ItemID
   int ParentID 
   string Name
}

Item 实际上是一个更大的表 Object 的子集:

Object
{
    int ObjectID
    string Color
}

所以ItemIDObjectID 的FK。在Item 中,ParentID 可以引用另一个 Item 或父 Object。

我想要做的是能够从 Item 关系中的叶子一直迭代到其父级,直到我最终可以确定给定 Item 叶子的 ObjectID 来自什么。

我想在 SQL 中执行此操作。我正在使用 SQL Server 2008。

这就是我的想法。我可以遍历 Item ParentID,直到我不能再将 ParentID 与另一个 Item 一起加入。这个ParentID是我要返回的ObjectID

我一直在尝试使用内部连接来使其工作,但没有运气。我正在使用 C#,所以如果这可以在 linq 中完成,我不怀疑它可以在没有非常低效的情况下完成,那也很好。

我的回答是:

WITH ObjectH (ParentID, ItemID, Level) AS
(
   -- Base case
   SELECT
      ParentID,
      ItemID,
      1 as Level 
   FROM Item
   WHERE ItemID = @param 

   UNION ALL

   -- Recursive step
   SELECT
      i.ParentID,
      i.ItemID,
      oh.Level + 1 AS Level
   FROM Item i
      INNER JOIN ObjectH oh ON
         c.ItemID = oh.ParentID           
)

SELECT TOP 1 ParentID
FROM ObjectH
ORDER BY Level DESC

这行得通。

【问题讨论】:

    标签: c# sql sql-server linq


    【解决方案1】:

    我尝试使用递归 SQL Server CTE 来执行此操作。

    从逻辑上讲,这就是 CTE 的工作方式,应该是这样,但我从未将它与您的架构完全相同。

    SQL 中的树结构通常没有额外的父“对象”表。 (似乎你也在数据库中以 1:1 的比例模仿你的对象设计?如果我是你,我会重新审视这种模式

    尝试一下,如果它有效,请告诉我。

    编辑:稍微更改了基本查询条件

    EDIT2:更改了查询以使其特定于一项

    WITH ObjectHierarchy (ItemID, Name, ParentID, Level) AS
    (
       SELECT 
          ItemID, 
          Name, 
          ParentID, 
          1 as Level 
       FROM Item it, Object ob
       WHERE it.ParentID = ob.ObjectID
       AND ob.ItemID = @itemIdToBeSearched
    
       UNION ALL
    
       SELECT
          i.ItemID,
          i.Name,
          i.ParentID,
          oh.Level + 1 AS Level
       FROM Item i
       INNER JOIN ObjectHierarchy oh ON
          i.ParentID = oh.ItemID
       AND oh.ItemID = @itemIdToBeSearched
    
    )
    
    SELECT parentID
    FROM ObjectHierarchy 
    WHERE LEVEL = 1
    

    【讨论】:

    • 代码有效,因为它列出了所有的关系,但是如何在给定任何 ItemID 的情况下选择 ObjectID?
    • 它现在只返回一个庞大的 ParentID 列表。我可以知道查询在做什么,所以我可以让它从这里开始工作。我不是 SQL 文盲,但我只是不够好做递归的东西。不过感谢您的编辑!
    • 列表包含多个 Level=1。它列出了所有可能的 Level=1。我一直在看它,据我所知它应该可以工作。
    • 再次感谢,但它抱怨这一行:AND oh.ItemID = @itemIdToBeSearched 无法绑定多部分标识符“oh.ItemID”。最上面的那个。底部 AND 运行。我非常讨厌这个 DB 的设计,但我对此无能为力。
    • 你的最后一次更新没有用,但我最终让它工作了。我从 SQL 中删除了对象,并在我的问题中发布了我的工作代码。也许您可以帮助解决对 Top 1 的需求。至少我有一些有用的东西,所以我可以做我现在真正需要做的事情。感谢您的帮助。
    【解决方案2】:

    首先,允许 ParentID 列将两个不同的表作为“外键”引用是一种糟糕的设计。由于您在编写此查询时遇到困难,您可能已经意识到这一点。

    话虽如此,并且假设您处于无法重新设计架构的地步,我将通过创建一个表示“应该”的 Item 的视图来解决这个问题,然后使用传统的递归该视图上的 CTE 方法来遍历层次结构。

    create view vwItem 
    as
    select i.ItemID, 
           case when o.ObjectID is null then i.ParentID else null end as ParentID, 
           o.ObjectID, Name
        from Item i
            left join Object o
                on i.ParentID = o.ObjectID
    go
    
    ;with cteItemList as (
        select i.ItemID, i.ParentID, i.ObjectID, i.Name, 1 as Level
            from vwItem i
            where i.ParentID is null
        union all
        select i.ItemID, i.ParentID, i.ObjectID, i.Name, il.Level+1 as Level
            from vwItem i
                inner join cteItemList il
                    on i.ParentID = il.ItemID
    )
    select *
        from cteItemList
        order by Level, ItemID
    

    【讨论】:

    • 完全同意你的看法,很高兴你认为 CTE 是传统的!
    【解决方案3】:

    只需在 SQL 中创建一个为您迭代的标量函数,并将其包含在一个模拟您的表的视图中,但将该函数的结果添加为一列。然后,您将 LINQ 对象基于视图,并在代码中拥有 ParentID。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-01-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-03-13
      • 2021-11-18
      • 2015-02-18
      • 2018-08-02
      相关资源
      最近更新 更多