在递归 SQL 查询中使用全局列表来避免访问节点

Posted

技术标签:

【中文标题】在递归 SQL 查询中使用全局列表来避免访问节点【英文标题】:Using global list in recursive SQL query to avoid visted nodes 【发布时间】:2020-08-03 10:30:44 【问题描述】:

我有一个自引用表用户

id          | follower
------------|------------
1 (adam)    | 2 (bob)
1 (adam)    | 3 (charlie)
2 (bob)     | 1 (adam)
2 (bob)     | 3 (charlie)

注意有循环引用。

我想获取用户的所有关注者,关注者的关注者等等,以便所有关注者都显示在一个扁平列表中,并具有各自的深度

对于亚当:

id | follower    | depth
---|-------------|-------
1  | 1 (bob)     | 0
2  | 3 (charlie) | 0
3  | 1 (adam)    | 1 (bob -> adam)
4  | 3 (charlie) | 1 (bob -> charlie)

问题

我想避开第 3 行和第 4 行,这代表了两个问题:

    adam -> bob -> adam 因为它是圆形的。

    adam -> bob -> charlie因为charlie之前已经出现过。

我可以通过在分支中保留访问过的ids 的path 列来使用以下查询来解决问题 #1

WITH RECURSIVE cte AS (
  SELECT id, follower, 0 as depth, ARRAY[id] AS path
  FROM user
  UNION ALL
  SELECT id, follower, depth + 1, id || path
  FROM user
  JOIN cte ON user.id = cte.follower
  WHERE NOT path @> Array[user.id]
)
SELECT * from cte

但它不能解决问题 #2。

它给出以下结果:

follower    | depth | path
------------|-------|-----
2 (bob)     | 0     | 2
3 (charlie) | 0     | 3
3 (charlie) | 1     | 2, 3

它仍然存在问题#2(重复的charlie 条目),因为path 列仅在特定分支中保留ids 的列表。

如何解决问题 #2?

可能的解决方案

我可以在我的代码 (Node.JS) 中通过保持 global 缓存(path 等效项)来解决它。

const list = ; /* <-- GLOBAL cache */
function recurse(user, depth = 0) 
  for(const  id, followers  of user.followers) 
    if (!(id in list)) 
      list[id] = id, depth
      recurse( followers , depth + 1);
    
  

然而,据我所知,上面的 SQL 查询相当于:

function recursive() 
  const list = ; /* <-- LOCAL cache */
  for(const id of followers)
    if (!(id in list)) ...

如何使用 SQL 中的全局缓存在代码中复制我的解决方案?

或者有什么其他方法可以达到预期的效果?

我正在使用 Node.JS 和 PostgreSQL

【问题讨论】:

【参考方案1】:

如果我理解正确,您希望在递归搜索后每个关注者只选择一行:

WITH RECURSIVE cte AS (
      SELECT id, follower, 0 as depth, ARRAY[id] AS path
      FROM user
      UNION ALL
      SELECT id, follower, depth + 1, id || path
      FROM user
      JOIN cte ON user.id = cte.follower
      WHERE NOT path @> Array[user.id]
     )
SELECT DISTINCT ON (follower) *
FROM cte
ORDER BY follower, depth;

【讨论】:

我有没有机会递归搜索本身(使用全局变量或其他东西)这样做?我知道这在这种情况下可能会起作用,但我最初的问题有点不同,我虽然以这种方式改写它可能会给我一个解决方案。我原来的问题是this。基本上我的查询很慢,因为我无法在搜索查询本身 during 中消除所需的重复条目 - 所以这样做的解决方案将是理想的. 显而易见的方法是将递归替换为存储过程中的循环。这样可以在每个循环之后过滤数据。

以上是关于在递归 SQL 查询中使用全局列表来避免访问节点的主要内容,如果未能解决你的问题,请参考以下文章

我创建了一个文件来访问所有全局变量。我无法访问 pyspark-sql 查询中定义的 UDF 中的全局变量

如何在递归 SQL 查询中查找子树中的所有节点?

SQL优化

sql 知道父节点,查询所有的子节点,运用游标,递归,存储过程

如何避免在递归中使用全局/类级别变量?

sql 知道父节点,查询所有的子节点,运用游标,递归,存储过程