将 CTE 与分层数据和 'cumulative' 值一起使用

Using CTE with hierarchical data and 'cumulative' values

我正在试验 SQL 常见 Table 表达式,使用城市、国家和大洲的示例层次结构以及哪些已访问过哪些尚未访问。

table t_hierarchy 看起来像这样:

(注意:non-cities 的 visited 列故意为 NULL,因为我希望这是一个动态计算的百分比。)

然后我使用以下 SQL 根据 t_hierarchy 中的数据创建了一个递归结果集:

WITH myCTE (ID, name, type, parentID, visited, Depth)
 AS
 (
    Select ID, name, type, parentID, visited, 0 as Depth From t_hierarchy where parentID IS NULL
    UNION ALL
    Select t_hierarchy.ID, t_hierarchy.name, t_hierarchy.type, t_hierarchy.parentID, t_hierarchy.visited, Depth + 1 
    From t_hierarchy 
    inner join myCte on t_hierarchy.parentID = myCte.ID
 )

Select ID, name, type, parentID, Depth, cnt.numDirectChildren, visited
FROM myCTE
LEFT JOIN (
          SELECT  theID = parentID, numDirectChildren = COUNT(*)
          FROM    myCTE
          GROUP BY parentID
        ) cnt ON cnt.theID = myCTE.ID

 order by ID

结果如下所示:

我现在想做的是创建一个专栏,例如visitedPercentage 以显示 访问过的城市的百分比 每个 'level' 层次结构(将城市与国家和大洲区别对待)。解释一下,我们一直在 'tree':

我希望这是有道理的。我有点想说“如果它 不是 一个城市,根据所有直接 children 的 visitedPercentage 计算出这个级别的 visitedPercentage , 否则只显示 100% 或 0%。非常感谢任何指导。


更新: 我已经使用 Daniel Gimenez 的建议将它进一步推进到我得到法国 100、西班牙 50 等的地步。但是顶级项目(例如欧洲)仍然是 0,如下所示:

我认为这是因为计算是在查询的递归部分之后而不是在其中完成的。 IE。这一行:

SELECT... , visitPercent = SUM(CAST visited AS int) / COUNT(*) FROM myCTE GROUP BY parentID

说的是 "look at the visited column for child objects, calculate the SUM of the values, and show the result as visitPercent",而它应该说的是 "look at the existing visitPercent value from the previous calculation",如果这有意义的话。我不知道从这里去哪里! :)

我想我已经做到了,使用 2 个 CTE。最后,更容易获得每个级别(子孙等)的后代总数,并用它来计算总体百分比。

那很痛苦。有一次输入 'CATS' 而不是 'CAST' 让我困惑了大约 10 分钟。

with cte1 (ID,parentID,type,name,visited,Lvl) as (
    select t.ID, t.parentID, t.type, t.name, t.visited, 0 as [Lvl]
    from t_hierarchy t
    where t.parentID is not null
    union all
    select c.ID, t.parentID, c.type, c.name, c.visited, c.Lvl + 1
    from t_hierarchy t
        inner join cte1 c on c.parentID = t.ID
    where t.parentID is not null
),
cte2 (ID,name,type,parentID,parentName_for_reference,visited,Lvl) as (
    Select t_hierarchy.ID, t_hierarchy.name, t_hierarchy.type, t_hierarchy.parentID, p.name as parentName_for_reference, t_hierarchy.visited, 0 as Lvl
        From t_hierarchy
        left join t_hierarchy p ON p.ID = t_hierarchy.parentID
        where t_hierarchy.parentID IS NULL
    UNION ALL
    Select t_hierarchy.ID, t_hierarchy.name, t_hierarchy.type, t_hierarchy.parentID,p.name as parentName_for_reference, t_hierarchy.visited, Lvl + 1 
    From t_hierarchy
    inner join cte2 on t_hierarchy.parentID = cte2.ID
    inner join t_hierarchy p ON p.ID = t_hierarchy.parentID
)

select cte2.ID,cte2.name,cte2.type,cte2.parentID,cte2.parentName_for_reference,cte2.visited,cte2.Lvl
,CASE WHEN type = 'city' THEN 'N/A' ELSE CAST(cnt.totalDescendents as varchar) END AS totalDescendents
,CASE WHEN type = 'city' THEN 'N/A' ELSE CAST(COALESCE(cnt2.totalDescendentsVisited,0) as varchar) END AS totalDescendentsVisited
,CASE WHEN type = 'city' THEN 'N/A' ELSE CAST((CAST(ROUND(CAST(COALESCE(cnt2.totalDescendentsVisited,0) as float)/CAST(cnt.totalDescendents as float),2) AS numeric(36,2))*100) as varchar) END as asPercentage
from cte2
left JOIN (
     SELECT  theID = parentID, COUNT(*) as totalDescendents
     FROM cte1
     WHERE type = 'city'
     GROUP BY parentID
  ) cnt ON cnt.theID = cte2.ID
 left JOIN (
     SELECT  theID = parentID, COUNT(*) as totalDescendentsVisited
     FROM cte1
     WHERE type = 'city' AND visited = 1
     GROUP BY parentID
  ) cnt2 ON cnt2.theID = cte2.ID
ORDER BY ID

这些帖子很有帮助:

  • Keeping it simple and how to do multiple CTE in a query

  • CTE to get all children (descendants) of a parent