【问题标题】:Deeply nested subqueries for traversing trees in MySQL用于在 MySQL 中遍历树的深度嵌套子查询
【发布时间】:2025-12-25 16:45:07
【问题描述】:

我的数据库中有一个表,我使用混合嵌套集 (MPTT) 模型(具有 lftrght 值的模型)和邻接列表模型(存储 parent_id on每个节点)。

my_table (id, parent_id, lft, rght, alias)

这个问题与树的任何 MPTT 方面都没有关系,但我想我会留下它,以防有人知道如何利用它。

我想将别名路径转换为特定节点。例如:"users.admins.nickf" 会找到别名为“nickf”的节点,该节点是别名为“admins”的节点的子节点,而别名“admins”是位于根目录下的“users”的子节点。 (parent_id, alias) 上有一个唯一索引。

我从编写函数开始,以便将路径拆分为各个部分,然后逐个查询数据库:

SELECT `id` FROM `my_table` WHERE `parent_id` IS NULL AND `alias` = 'users';-- 1
SELECT `id` FROM `my_table` WHERE `parent_id` = 1 AND `alias` = 'admins';   -- 8
SELECT `id` FROM `my_table` WHERE `parent_id` = 8 AND `alias` = 'nickf';    -- 37

但后来我意识到我可以通过一个查询,使用可变数量的嵌套来做到这一点:

SELECT `id` FROM `my_table` WHERE `parent_id` = (
    SELECT `id` FROM `my_table` WHERE `parent_id` = (
        SELECT `id` FROM `my_table`
        WHERE `parent_id` IS NULL AND `alias` = 'users'
    ) AND `alias`  = 'admins'
) AND `alias` = 'nickf';

由于子查询的数量取决于路径中的步骤数,我是否会遇到子查询过多的问题? (如果有这种事)

有没有更好/更智能的方法来执行这个查询?

【问题讨论】:

  • 何不建个测试表,看看查询(递归)栈有多深?
  • 不能首先使用最具辨别力的选择器('nickf')'感觉不对'..也许你通过加入查询而不是使用子查询来构建它?这将避免递归(不是真的,但看起来像)=>级联自加入!
  • 出于好奇,我写了“自加入级联”.. 看看我的答案。

标签: sql mysql tree adjacency-list mptt


【解决方案1】:

这行得通吗?

select r0.id 
  from my_table as r0 
  join my_table as r1 on(r0.parent_id = r1.id) 
  join my_table as r2 on(r1.parent_id = r2.id)
 where r0.alias='nickf'
   and r1.alias='admins'
   and r2.alias='users'
   and r2.parent_id is null

在我看来,实际上并不需要嵌套子查询..

还是我错了,遗漏了什么?

【讨论】:

    【解决方案2】:

    我自己也想知道这一点,并且正在寻找随着您深入而不会变慢的东西(这意味着上述两个选项中的更多级别。)我对“我的版本”的问题是它必须创建在将结果缩小到您实际搜索的结果之前的每条可能的路径......所以我认为 lexu 的版本即使对于非常大的嵌套也应该优于我的版本,因为它是一个简单的连接,但我希望有人可能会看到它并且希望进一步扩展。

    此外,这样做肯定会受益于存储过程和/或它的“路径”部分的视图(没有 HAVING 子句)。也许有了这些,它是一个更好的解决方案,但不幸的是,我目前对 SQL 性能的了解还不够,无法肯定地说。我可以说随着数据(可能的路径组合的数量)变大,我的速度会变慢,但是从视图(因为结果被缓存,并使用它来缩小范围)看起来很快(我发现的最大数据集总共是 370 个,在某些时候我会创建一个更大的集合来测试。)

    SELECT node.id, GROUP_CONCAT(parent.alias
                     ORDER BY parent.lft SEPARATOR '.') AS path_name
    FROM my_table AS node, my_table AS parent
    WHERE node.lft BETWEEN parent.lft AND parent.rght
    GROUP BY node.id HAVING path_name = 'users.admins.nickf'
    

    【讨论】:

    • 遇到这种情况的任何人,我都使用 MySQL 中的大型数据集对其进行了测试。它非常慢,不幸的是,视图的使用没有帮助。