如何创建非自引用 MySQL 递归查询

How to create a non-self-referencing MySQL recursive query

这里有一个相关问题:SO - Mysql Recursive Query

我正在尝试确定如何在 mysql(版本>=8)中创建递归查询,它可以收集所有以某种方式 'linked' 到原始行的行。结构比较简单:

CREATE TABLE link (
  id INT,
  left_id INT,
  right_id INT
);

CREATE TABLE item (
  id INT,
  content VARCHAR(100)
);

INSERT INTO item (id, content) VALUES (1, 'first linked item');
INSERT INTO item (id, content) VALUES (2, 'unlinked item');
INSERT INTO item (id, content) VALUES (3, 'second linked item');
INSERT INTO item (id, content) VALUES (4, 'third linked item');
INSERT INTO item (id, content) VALUES (5, 'fourth linked item');
INSERT INTO item (id, content) VALUES (6, 'second group item 1');
INSERT INTO item (id, content) VALUES (7, 'second group item 2');

INSERT INTO link (id, left_id, right_id) VALUES (1, 1, 3);
INSERT INTO link (id, left_id, right_id) VALUES (2, 4, 3);
INSERT INTO link (id, left_id, right_id) VALUES (2, 5, 1);
INSERT INTO link (id, left_id, right_id) VALUES (4, 6, 7);

这里我们创建了 "item" 行。稍后我们可以通过向 "link" table 添加一行来决定任意 link 一项到另一项。 "left_id" 和 "right_id" 只是一个项目和另一个项目的id。这意味着 link 完成一项作为左侧或右侧没有什么不同。

查询应从一个或多个指定项目开始,找到 links 匹配 left_id 或 right_id 并将项目添加到结果中,然后对于这些新行,检查更多 link 等,直到还剩 none。

对于上面提供的测试数据,我预计:

Calling query with condition "content = 'first linked item'":
Result: 
1 | 'first linked item'
3 | 'second linked item'
4 | 'third linked item'
5 | 'fourth linked item'
Calling query with condition "content = 'second group item 2'":
Result:
6 | 'second group item 1'
7 | 'second group item 2'

mysql8+ 中有哪些功能可以有效地调用此类查询?

您可以使用递归 CTE 遍历图表。例如:

with
recursive i as (
  select id, content, -1 as source 
  from item where content = 'first linked item'
 union all
  select m.id, m.content, i.id
  from i
  join link l on l.left_id = i.id or l.right_id = i.id
  join item m on m.id <> i.id and m.id <> i.source and m.id = l.left_id
              or m.id <> i.id and m.id <> i.source and m.id = l.right_id
)
select id, content from i;

然后,只需更改第 3 行中的搜索条件,从不同的项目开始。

只要图形没有循环,这个解决方案就可以很好地工作。

编辑:处理循环的解决方案

根据要求,这是适用于循环的解决方案:

with
recursive i as (
  select id, content, concat(',', id, ',') as route 
  from item where content = 'first linked item'
 union all
  select m.id, m.content, concat(i.route, m.id, ',')
  from i
  join link l on l.left_id = i.id or l.right_id = i.id
  join item m on i.route not like concat('%,', m.id, ',%')
    and (m.id = l.left_id or m.id = l.right_id)
)
select distinct id, content from i;

参见 DB Fiddle 我在其中介绍了一个循环。