【问题标题】:Follow the connections : recursive query遵循连接:递归查询
【发布时间】:2021-08-09 06:16:50
【问题描述】:

我有两列数字。 目标是从数字 f.e. 开始。 55,提取所有“连接”的数字 (从这些数字中的任何一个开始,应该会产生相同的结果)

 A  B
-----
56  55
55  56
69  55
35  55
47  55
60  55
22  55
26  47
 2  35
..... more data

在这种情况下,此处显示的所有数字:55,56,35,69,60,22,47,2,26

我使用以下查询:

with recursive merge as (
   select A from T where A = 55 or B = 55
   union 
   select B 
    from T cm join merge m 
       on cm.A = m.A or cm.B = m.A
)
select * from merge

但我只取回那些:55,56,35,69,60,22,47

我认为使用它会起作用:

with recursive merge as (
   select A from T where A = 55 or B = 55
   union 

  (
   select B 
    from T cm join merge m 
       on cm.A = m.A
    union
   select A 
    from T cm join merge m 
       on cm.B = m.A
  )
)
select * from merge

但是 Postgres 不允许在递归查询中多次使用“合并”!!?

我的目标是从数字开始找到“链”中所有连接的数字,即

  55 => 56,35,69,60,22,47 => 2,26 ....

因为:

  47 => 26
  35 => 2

【问题讨论】:

    标签: sql postgresql recursion connection


    【解决方案1】:

    这是一个无向图。因此,一种解决方案是创建所有边缘,然后跟随它们。

    为避免无限循环,请保留已访问节点的列表:

    with recursive pairs as (
          select a, b
          from t
          union  -- on purpose to remove duplicates
          select b, a
          from t
         ),
         cte as (
          select p.a, p.b, array[p.a, p.b] as els
          from pairs p
          where a = 55
          union all
          select p.a, p.b, els || p.b
          from cte join
               pairs p
               on cte.b = p.a
          where not (p.b = any (cte.els))
    )
    select b
    from cte;
    

    Here 是一个 dbfiddle。

    【讨论】:

      【解决方案2】:

      最后两个 CTE UNIONed 怎么样?

      WITH RECURSIVE
      m1
      AS
      (
      SELECT t.a
             FROM t
             WHERE a = 55
                    OR b = 55
      UNION
      SELECT t.b 
             FROM t
                  INNER JOIN m1
                             ON t.a = m1.a
      ),
      m2
      AS
      (
      SELECT t.a
             FROM t
             WHERE a = 55
                    OR b = 55
      UNION
      SELECT t.a
             FROM t
                  INNER JOIN m1
                             ON t.b = m1.a
      )
      SELECT m1.a
             FROM m1
      UNION
      SELECT m2.a
             FROM m2;
      

      db<>fiddle

      【讨论】:

        【解决方案3】:

        你可以这样做:

        with recursive
        n (v) as (
          select case when t.a = 55 then t.b else t.a end
          from t
          where t.a = 55 or t.b = 55
         union
          select case when t.a = n.v then t.b else t.a end
          from n
          join t on t.a = n.v or t.b = n.v
        )
        select * from n
        

        结果:

         v
        --
        56
        69
        35
        47
        60
        22
        55
         2
        26
        

        请参阅DB Fiddle 的运行示例。

        或者...如果您希望将参数放在一个位置:

        with recursive
        params as (select 55 as x), -- parameter only once
        n (v) as (
          select case when t.a = p.x then t.b else t.a end
          from t
          cross join params p 
          where t.a = p.x or t.b = p.x
         union
          select case when t.a = n.v then t.b else t.a end
          from n
          join t on t.a = n.v or t.b = n.v
        )
        select * from n
        

        【讨论】:

        • 我可以在更新中使用这个查询吗:update A = 1 where A in (select v from n)
        • 是的,您可以将它用于UPDATE。见postgresql.org/docs/13/sql-update.html
        • 任何将其拆分的想法或任何建议以加快速度...在〜100_000条记录上使用它并且非常慢..一旦我为每100_000个X调用它,它就会减少到~20_000 又一次......而且我必须将其扩展到至少 100 万......这需要一天的时间来处理:(
        • @sten 如果您希望查询“遍历”100k 个节点,则需要添加索引。表有哪些索引?我认为您应该针对特定表及其具有的索引提出一个新问题。此外,请提供查询正在使用的当前执行计划。
        • 它在两个列上都有 btree 索引......你是对的,我可能会把它作为另一个问题来表述