【问题标题】:Use table alias in another query to traverse a tree在另一个查询中使用表别名来遍历树
【发布时间】:2014-08-28 15:56:42
【问题描述】:

我有以下疑问:

select * from (
   select p1.c as child, p1.p as parent, 1 as height
   from parent as p1
   where (p1.c=3 or p1.c=8);

   union

   select p2.c as child, p2.c as parent, 2 as height
   from parent as p2 
   where (p1.child=3 or p1.child=8) and p1.parent = p2.child;
)

架构是:

CREATE TABLE parent(p int, c int);

我正在尝试找到从 childroot 的路径。 [编辑] 并附加我们必须遍历的边数。
目标是将孩子的父母与其父母联系起来,例如:

(8, 2, 1)
(8, 5, 2) -> 8 is the lowest child, 2 is its parent, and 5 it's 2 parent.

一些样本数据:

10 | 5
10 | 12
12 | 3
12 | 4
4  | 6
4  | 7
5  | 11
5  | 2
2  | 8

如何在将形成p2 的第二个查询中使用第一个查询p1 的引用? 在那之后我应该有;

(8,2,1)
(3,12,1)
(3,10,2)
(8,5,2)

这样我就已经知道该怎么做才能完成我想要的了。

【问题讨论】:

  • 如果使用 Oracle,请查找 CONNECT BY,并可能混入一个 LISTAGG。
  • 这些例子对我来说很好。第三列是“高度”,即节点之间的距离。第一列是“孩子”,第二列是“父母”(身高 1)或“祖父母”(身高 2)。
  • @spencer7593:现在,那个加起来了。谢谢。
  • 您的树是像示例中那样限制为三个级别,还是您正在寻找 n 个级别的通用解决方案?
  • 为什么这个问题用 4 个不同的 DBMS 标记?

标签: mysql sql oracle sqlite postgresql


【解决方案1】:

问的问题

不能在同一级别的另一个查询(或UNION 查询的另一分支)中从一个子查询引用表别名。表别名仅在查询本身及其子查询中可见。
可以使用LATERAL JOIN 在同一查询级别上引用子查询的输出列。示例:
Find most common elements in array with a group by

最大关卡数较少的解决方案

对于少数几个级别(如果您知道最大值),您可以使用一个简单的查询:

  • LEFT JOIN 到表本身的 n-1 个实例
  • 使用COALESCECASE 语句确定根和高度,
SELECT p1.c AS child, COALESCE(p3.p, p2.p, p1.p) AS parent
      ,CASE
          WHEN p3.p IS NOT NULL THEN 3
          WHEN p2.p IS NOT NULL THEN 2
          ELSE 1
       END AS height
FROM   parent p1
LEFT   JOIN parent p2 ON p2.c = p1.p
LEFT   JOIN parent p3 ON p3.c = p2.p
WHERE  p1.c IN (3, 8)
ORDER  BY p1.c;

这是标准 SQL,应该在您标记的所有 4 个 RDBMS 中工作。

任意数量级别的通用解决方案

使用@Ken 已经建议的recursive CTE

  • 在每一行的递归分支中保留子节点,只推进父节点。
  • 在外部SELECT 中,每个孩子只保留height 最大的行。
WITH RECURSIVE cte AS (
   SELECT c AS child, p AS parent, 1 AS height
   FROM   parent
   WHERE  c IN (3, 8)

   UNION ALL

   SELECT c.child, p.p AS parent, c.height + 1
   FROM   cte    c
   JOIN   parent p ON p.c = c.parent
   -- WHERE  c.height < 10  -- to safeguard against endless loops if necessary
   )
SELECT DISTINCT ON (child) *
FROM   cte
ORDER  BY child, height DESC;

DISTINCT ON 特定于 Postgres。解释:
Select first row in each GROUP BY group?

其余部分将在 Oracle 甚至 SQLite 中以类似的方式工作,但在不支持 CTE 的 MySQL 中则不然。

SQL Fiddle 演示两者。

【讨论】:

    【解决方案2】:

    Postgres 中,我建议查看 WITH RECURSIVE CTE

    http://www.postgresql.org/docs/9.3/static/queries-with.html

    它非常适合建模树状结构,例如父子关系,无论是整棵树、子树等。

    Postgres 文档中有很多示例,包括专门处理树状结构的示例,这些示例应该适用于您的情况(尽管您的查询需要进行一些调整以适应 WITH RECURSIVE 模式)。

    【讨论】:

      【解决方案3】:

      不能在第二个 SELECT 中从第一个 SELECT 引用表别名(在 UNION 或 UNION ALL 集合运算符之后。您可以在相关子查询中,但必须在同一个 SELECT 的上下文中。)

      另外,你需要去掉语句中的分号; MySQL 会将其视为语句的结尾。

      对于MySQL,您肯定有一种可行的方法来解决这个问题,对每个“高度”使用单独的查询,并将结果与​​UNION 集合运算符结合起来。您可能希望使用 UNION ALL 集合运算符来提高性能。 (如果您不需要识别和删除重复项的额外步骤。另外,也没有必要使用内联视图。

      SELECT p1.c  AS child
           , p1.p  AS parent
           , 1     AS height
        FROM parent p1
       WHERE p1.c IN (3,8)
       UNION ALL
      SELECT p1.c  AS child
           , p2.p  AS parent
           , 2     AS height
        FROM parent p1
        JOIN parent p2
          ON p2.c = p1.p
       WHERE p1.c IN (3,8)
      

      只是为了演示如何将其扩展到 3 的高度,在同一个表中添加另一个 JOIN,别名为 p3

       UNION ALL
      SELECT p1.c  AS child
           , p3.p  AS parent
           , 3     AS height
        FROM parent p1
        JOIN parent p2 ON p2.c = p1.p
        JOIN parent p3 ON p3.c = p2.p
       WHERE p1.c IN (3,8)
      

      注意

      最初,我只注意到这是为 MySQL 标记的。

      使用 Oracle,您可以使用递归的 CONNECT BY 形式的 SELECT,在没有 UNION 的情况下获得等效结果。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-09-13
        • 1970-01-01
        • 2017-04-09
        • 1970-01-01
        相关资源
        最近更新 更多