计算后代节点 [来自 MySQL 闭包模式]

Counting Descendant Nodes [from MySQL Closure Pattern]

我正在使用以下数据结构来表示数据的层次结构。

用户Table

+----+------+
| id | name |
+----+------+
|  1 | Bob  |
|  2 | Sam  |
|  3 | Joe  |
|  4 | Kirk |
|  5 | Greg |
+----+------+

关系结束Table

+----------+------------+-------+
| ancestor | descendant | depth |
+----------+------------+-------+
|        1 |          1 |     0 |
|        1 |          2 |     1 |
|        1 |          3 |     2 |
|        1 |          4 |     2 |
|        1 |          5 |     3 |
|        2 |          2 |     0 |
|        2 |          3 |     1 |
|        2 |          4 |     1 |
|        2 |          5 |     2 |
|        3 |          3 |     0 |
|        4 |          4 |     0 |
|        4 |          5 |     1 |
|        5 |          5 |     0 |
+----------+------------+-------+

以上数据表示如下(英文-ese):

  1. 鲍勃有一个儿子:山姆
  2. Sam 有两个儿子:Joe 和 Kirk。
  3. 乔没有儿子。
  4. 柯克有一个儿子:格雷格。

我正在从以下 SQL 中获取给定用户的儿子:

SELECT u.*
FROM closure AS c
    INNER JOIN `user` AS u ON (u.id = c.descendant)
WHERE c.ancestor = 1 AND c.depth = 1

这很好用。但是我还想return树下的子孙数量。到目前为止我能想到的最好的是:

SELECT 
    u.*,
    (
        SELECT COUNT(id) FROM `user` WHERE id IN (
            SELECT descendant FROM closure 
            WHERE ancestor = c.descendant
        )
    ) AS descendant_count
FROM closure AS c
    INNER JOIN `user` AS u ON (u.id = c.descendant)
WHERE c.ancestor = 1 AND c.depth = 1

上述查询的预期输出为:

+----+------+------------------+
| id | name | descendant_count |
+----+------+------------------+
|  2 | Sam  |                3 |
+----+------+------------------+

问题(终于)

有没有比我现有的更好的求总数的方法?所有那些子 select 都是恶心的。

更新

当我看到这里时,我意识到对于这个例子我可能把事情简化了太多。我有两个 sub-selects 来做计数,因为我实际上有 3 个表:类别;物品; category_closure。在我的示例数据中,显然不需要双重嵌套子 select。在我的实际数据中有。希望这是有道理的。

您不需要子查询。您可以通过再次加入闭包 table 来获取每个 child 的后代数量,以找到其祖先是相应 child 的所有节点。然后使用 GROUP BY 这样你就可以得到每个 child.

的计数
SELECT 
    u.*,
    COUNT(*) AS descendant_count
FROM closure AS c
    INNER JOIN `user` AS u ON (u.id = c.descendant)
    INNER JOIN closure AS d ON (c.descendant = d.ancestor)
WHERE c.ancestor = 1 AND c.depth = 1
GROUP BY c.descendant