递归SQL-计算层次结构中的子代数

递归SQL-计算层次结构中的子代数,sql,postgresql,recursion,common-table-expression,hierarchical-data,Sql,Postgresql,Recursion,Common Table Expression,Hierarchical Data,考虑具有以下列的数据库表: 数学家 名字 顾问1 顾问2 数据库表示来自的数据,每个数学家通常有一个顾问,但有时有两个顾问 视觉辅助,使事情更清楚: 我如何计算每个数学家的后代数量 我可能应该使用常用的表表达式(带有递归),但我现在几乎被卡住了。我发现的所有类似示例都涉及只有一个父级而不是两个父级的层次结构 更新: 我调整了提供的SQL Server解决方案,使其也适用于PostgreSQL: WITH RECURSIVE cte AS ( SELECT m.id as start

考虑具有以下列的数据库表:

  • 数学家
  • 名字
  • 顾问1
  • 顾问2
数据库表示来自的数据,每个数学家通常有一个顾问,但有时有两个顾问

视觉辅助,使事情更清楚:

我如何计算每个数学家的后代数量

我可能应该使用常用的表表达式(带有递归),但我现在几乎被卡住了。我发现的所有类似示例都涉及只有一个父级而不是两个父级的层次结构

更新:

我调整了提供的SQL Server解决方案,使其也适用于PostgreSQL:

WITH RECURSIVE cte AS (
    SELECT m.id as start_id,
        m.id,
        m.name,
        m.advisor1,
        m.advisor2,
        1 AS level
    FROM public.mathematicians AS m

    UNION ALL

    SELECT cte.start_id,
        m.id,
        m.name,
        m.advisor1,
        m.advisor2,
        cte.level + 1 AS level
    FROM public.mathematicians AS m
    INNER JOIN cte ON cte.id = m.advisor1
               OR cte.id = m.advisor2
),
cte_distinct AS (
    SELECT DISTINCT start_id, id 
    FROM cte
)
SELECT cte_distinct.start_id,
    m.name,
    COUNT(*)-1 AS descendants_count
FROM cte_distinct
INNER JOIN public.mathematicians AS m ON m.id = cte_distinct.start_id
GROUP BY cte_distinct.start_id, m.name
ORDER BY cte_distinct.start_id

你没有说你用的是什么数据库管理系统。本例中我将使用SQL Server,但它也适用于支持递归查询的其他数据库

样本数据

我只输入了树的正确部分,从Euler开始

最有趣的部分是拉格朗日和狄里克莱之间的多重路径

DECLARE @T TABLE (ID int, name nvarchar(50), Advisor1ID int, Advisor2ID int);

INSERT INTO @T (ID, name, Advisor1ID, Advisor2ID) VALUES
(1,  'Euler', NULL, NULL),
(2,  'Lagrange', 1, NULL),
(3,  'Laplace', NULL, NULL),
(4,  'Fourier', 2, NULL),
(5,  'Poisson', 2, 3),
(6,  'Dirichlet', 4, 5),
(7,  'Lipschitz', 6, NULL),
(8,  'Klein', NULL, 7),
(9,  'Lindemann', 8, NULL),
(10, 'Furtwangler', 8, NULL),
(11, 'Hilbert', 9, NULL),
(12, 'Taussky-Todd', 10, NULL);
这就是它的样子:

SELECT * FROM @T;

+----+--------------+------------+------------+
| ID |     name     | Advisor1ID | Advisor2ID |
+----+--------------+------------+------------+
|  1 | Euler        | NULL       | NULL       |
|  2 | Lagrange     | 1          | NULL       |
|  3 | Laplace      | NULL       | NULL       |
|  4 | Fourier      | 2          | NULL       |
|  5 | Poisson      | 2          | 3          |
|  6 | Dirichlet    | 4          | 5          |
|  7 | Lipschitz    | 6          | NULL       |
|  8 | Klein        | NULL       | 7          |
|  9 | Lindemann    | 8          | NULL       |
| 10 | Furtwangler  | 8          | NULL       |
| 11 | Hilbert      | 9          | NULL       |
| 12 | Taussky-Todd | 10         | NULL       |
+----+--------------+------------+------------+
查询

这是一个具有两个有趣点的经典递归查询

1)
CTE
的递归部分使用
Advisor1ID
Advisor2ID
连接到锚定部分:

        INNER JOIN CTE 
            ON CTE.ID = T.Advisor1ID
            OR CTE.ID = T.Advisor2ID
2) 由于有可能有多条到子体的路径,递归查询可能会多次输出节点。为了消除这些重复,我在
CTE\u DISTINCT
中使用了
DISTINCT
。也许可以更有效地解决这个问题

为了更好地理解查询是如何工作的,请分别运行每个CTE并检查中间结果

WITH
CTE
AS
(
    SELECT
        T.ID AS StartID
        ,T.ID
        ,T.name
        ,T.Advisor1ID
        ,T.Advisor2ID
        ,1 AS Lvl
    FROM @T AS T

    UNION ALL

    SELECT
        CTE.StartID
        ,T.ID
        ,T.name
        ,T.Advisor1ID
        ,T.Advisor2ID
        ,CTE.Lvl + 1 AS Lvl
    FROM
        @T AS T
        INNER JOIN CTE 
            ON CTE.ID = T.Advisor1ID
            OR CTE.ID = T.Advisor2ID
)
,CTE_Distinct
AS
(
    SELECT DISTINCT
        StartID
        ,ID
    FROM CTE
)
SELECT
    CTE_Distinct.StartID
    ,T.name
    ,COUNT(*) AS DescendantCount
FROM
    CTE_Distinct
    INNER JOIN @T AS T ON T.ID = CTE_Distinct.StartID
GROUP BY
    CTE_Distinct.StartID
    ,T.name
ORDER BY CTE_Distinct.StartID;
结果

+---------+--------------+-----------------+
| StartID |     name     | DescendantCount |
+---------+--------------+-----------------+
|       1 | Euler        |              11 |
|       2 | Lagrange     |              10 |
|       3 | Laplace      |               9 |
|       4 | Fourier      |               8 |
|       5 | Poisson      |               8 |
|       6 | Dirichlet    |               7 |
|       7 | Lipschitz    |               6 |
|       8 | Klein        |               5 |
|       9 | Lindemann    |               2 |
|      10 | Furtwangler  |               2 |
|      11 | Hilbert      |               1 |
|      12 | Taussky-Todd |               1 |
+---------+--------------+-----------------+
此处
genderantcount
将节点本身作为子体进行计数。如果希望叶节点的值为0而不是1,则可以从该结果中减去1


这里是。

[从技术上讲,您的“树”是DAG]您可以重新定义子体:如果从a到B至少存在一条(定向)路径,则B是a的子体。然后计算每个a的B数量。您使用什么数据库管理系统?我使用的数据库管理系统是PostgreSQL,但语法与SQL Server没有太大区别。非常感谢。