MySQL递归 - 根据给定的ID获取所有子代和父代

4

MySQL版本8.0 模式SQL

CREATE TABLE IF NOT EXISTS `department` (
  `id` INT NOT NULL,
  `name` VARCHAR(45) NOT NULL,
  `father` INT NULL,
  PRIMARY KEY (`id`),
  INDEX `fk_department_department_idx` (`father` ASC) VISIBLE,
  CONSTRAINT `fk_department_department`
    FOREIGN KEY (`father`)
    REFERENCES `department` (`id`)
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = InnoDB;

insert into department (id,name,father)
values
(1, 'dp1',null),
(2, 'dp2',null),
(3, 'dp3',1),
(4, 'dp4',1),
(5, 'dp5',2),
(6, 'dp6',4),
(7, 'dp7',6),
(8, 'dp8',6),
(9, 'dp9',6);

SET GLOBAL sql_mode=(SELECT REPLACE(@@sql_mode,'ONLY_FULL_GROUP_BY',''));
SET SESSION sql_mode = '';

我的查询:

WITH RECURSIVE cte_department AS (
      SELECT 
          d1.id, 
          d1.name, 
          d1.father
      FROM
          department d1
      WHERE
          d1.id=6
    UNION ALL
      SELECT 
          d2.id, 
          d2.name, 
          d2.father
      FROM
          department d2
      INNER JOIN cte_department cte ON cte.id = d2.father
)
SELECT * FROM cte_department;

结果:

id  name    father
6   dp6      4
7   dp7      6
8   dp8      6
9   dp9      6

我需要什么:

id  name    father
1   dp1      null
4   dp4      1
6   dp6      4
7   dp7      6
8   dp8      6
9   dp9      6

问题是:我可以获取所有子元素,但我需要在此查询中添加给定ID的所有父元素,例如ID 6。我陷入了困境。如果有人能帮我,请参考fiddle链接。

https://www.db-fiddle.com/f/g8YkE3hqsvaw8G9vdHPyyF/0

2个回答

4

递归部分可以有多个查询块。

WITH RECURSIVE cte_department AS (
      SELECT 
          d1.id,
          d1.name,
          d1.father,
          'Begin' state
      FROM
          department d1
      WHERE
          d1.id=6
    UNION ALL
      SELECT 
          d2.id,
          d2.name,
          d2.father,
          'Up'
      FROM
          department d2
      INNER JOIN
          cte_department cte
      ON
          cte.father = d2.id
      WHERE
          cte.state in ('Begin', 'Up')
    UNION ALL
      SELECT 
          d2.id,
          d2.name,
          d2.father,
          'Down'
      FROM
          department d2
      INNER JOIN
          cte_department cte
      ON
          cte.id = d2.father
      WHERE
          cte.state in ('Begin', 'Down')
)
SELECT
    id, name, father
FROM
    cte_department
ORDER BY
    father, id, name;

db<>fiddle上试用一下。


这是一个很棒的解决方案。我在Oracle中尝试过相同的操作,但是Oracle不允许在递归部分中使用第二个UNION ALL,即使我使用括号也无济于事。我必须将你的“Up”和“Down”查询合并为一个Up/Down查询。很棒的是MySQL允许在这里使用UNION ALL来让查询非常易读。感谢您的分享。 - Thorsten Kettner
很棒!MySQL允许在此处使用任意数量的递归块并混合使用UNION ALL/DISTINCT(但它不定义递归子查询处理的顺序,因此在某些情况下输出甚至可能是不确定的)。 - Akina
@Akina:我甚至还没有问问题。我不是OP :-) - Thorsten Kettner

4

我会使用两个单独的递归查询:一个用于获取子节点,另一个获取父节点,然后将结果使用union合并。您可以跟踪每个节点的级别,以便在结果集中正确排序记录。

with recursive 
    children as (
        select 1 as lvl, d.* from department d where id = 6
        union all
        select c.lvl, d.* from department d inner join children c on c.id = d.father
    ),
    parents as (
        select 1 as lvl, d.* from department d where id = 6
        union all
        select p.lvl - 1, d.* from department d inner join parents p on d.id = p.father
    )
select * from parents
union   -- on purpose, to remove the duplicate on id 6
select * from children
order by lvl;

相比在同一个查询中使用多个union all成员,这种方法更加安全。MySQL无法保证递归成员的评估顺序,因此使用这种技术可能会导致意外行为。

DB Fiddle上的演示


与您的问题无关,但以下内容可以在您的代码中看到:

SET GLOBAL sql_mode=(SELECT REPLACE(@@sql_mode,'ONLY_FULL_GROUP_BY',''));
SET SESSION sql_mode = '';

不要这样做。 ONLY_FULL_GROUP_BY 的存在是有充分理由的,它使MySQL在聚合查询方面的行为与SQL标准保持一致。禁用此SQL模式从未是一个好主意。


谢谢你的回答,以及关于ONLY_FULL_GROUP_BY的提示。 - Fábio
这就是精神啊,伙计——完美的回答,加9000声望。 - csstudent1418

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接