使用邻接表模型在MySQL中管理分层数据

时间:2019-11-20 08:52:29  来源:igfitidea点击:

简介:在本教程中,您将学习如何使用邻接表模型在MySQL中管理分层数据。

邻接表模型简介

分层数据无处不在。
它可以是博客类别,产品层次结构或组织结构。

在MySQL中,有许多方法可以管理层次结构数据,而邻接表模型可能是最简单的解决方案。
由于其简单性,邻接表模型是开发人员和数据库管理员的非常普遍的选择。

在邻接列表模型中,每个节点都有一个指向其父节点的指针。
顶层节点没有父节点。
请参阅以下电子产品类别:

在使用邻接表模型之前,您应该熟悉一些术语:

  • 电子是顶部节点或根节点。

  • 笔记本电脑,相机和照片,电话和配件节点是"电子"节点的子级。
    反之亦然,电子节点是"笔记本电脑,相机和照片,电话和配件"节点的父级。

  • 叶子节点是没有子节点的节点,例如Laptops,PC,Android,iOS等,而非叶子节点是至少有一个孩子的节点。

  • 节点的子孙称为子孙。
    节点的父母,祖父母等也称为祖先。

要对该类别树建模,我们可以创建一个名为category的表,该表具有三列:id,title和parent_id,如下所示:

CREATE TABLE category (
  id int(10) unsigned NOT NULL AUTO_INCREMENT,
  title varchar(255) NOT NULL,
  parent_id int(10) unsigned DEFAULT NULL,
  PRIMARY KEY (id),
  FOREIGN KEY (parent_id) REFERENCES category (id) 
    ON DELETE CASCADE ON UPDATE CASCADE
);

表中的每一行都是由id列标识的树中的节点。
parent_id列是类别表本身的外键。
它的作用类似于指向id列的指针。

插入资料

树的根节点没有父节点,因此,parent_id设置为NULL。
其他节点必须只有一个父节点。

要插入根节点,可将parent_id设置为NULL,如下所示:

INSERT INTO category(title,parent_id) 
VALUES('Electronics',NULL);

要插入非根节点,您只需要将其parent_id设置为其父节点的id即可。
例如,"笔记本电脑和PC","相机和照片"以及"电话和配件"节点的parent_id设置为1:

INSERT INTO category(title,parent_id) 
VALUES('Laptops & PC',1);

INSERT INTO category(title,parent_id) 
VALUES('Laptops',2);
INSERT INTO category(title,parent_id) 
VALUES('PC',2);

INSERT INTO category(title,parent_id) 
VALUES('Cameras & photo',1);
INSERT INTO category(title,parent_id) 
VALUES('Camera',5);

INSERT INTO category(title,parent_id) 
VALUES('Phones & Accessories',1);
INSERT INTO category(title,parent_id) 
VALUES('Smartphones',7);

INSERT INTO category(title,parent_id) 
VALUES('Android',8);
INSERT INTO category(title,parent_id) 
VALUES('iOS',8);
INSERT INTO category(title,parent_id) 
VALUES('Other Smartphones',8);

INSERT INTO category(title,parent_id) 
VALUES('Batteries',7);
INSERT INTO category(title,parent_id) 
VALUES('Headsets',7);
INSERT INTO category(title,parent_id) 
VALUES('Screen Protectors',7);

寻找根节点

根节点是没有父节点的节点。
换句话说,其parent_id为NULL:

SELECT
    id, title
FROM
    category
WHERE
    parent_id IS NULL;

查找节点的直接子代

以下查询获取根节点的直接子代:

SELECT
    id, title
FROM
    category
WHERE
    parent_id = 1;

查找叶节点

叶节点是没有子节点的节点。

SELECT
    c1.id, c1.title
FROM
    category c1
        LEFT JOIN
    category c2 ON c2.parent_id = c1.id
WHERE
    c2.id IS NULL;

查询整棵树

以下递归公用表表达式(CTE)检索整个类别树。
请注意,自MySQL 8.0起,CTE功能已可用

WITH RECURSIVE category_path (id, title, path) AS
(
  SELECT id, title, title as path
    FROM category
    WHERE parent_id IS NULL
  UNION ALL
  SELECT c.id, c.title, CONCAT(cp.path, ' > ', c.title)
    FROM category_path AS cp JOIN category AS c
      ON cp.id = c.parent_id
)
SELECT * FROM category_path
ORDER BY path;

查询子树

以下查询获取ID为7的Phone&Accessories子树。

WITH RECURSIVE category_path (id, title, path) AS
(
  SELECT id, title, title as path
    FROM category
    WHERE parent_id = 7
  UNION ALL
  SELECT c.id, c.title, CONCAT(cp.path, ' > ', c.title)
    FROM category_path AS cp JOIN category AS c
      ON cp.id = c.parent_id
)
SELECT * FROM category_path
ORDER BY path;

查询单个路径

要查询从底部到顶部的单个路径(例如,从iOS到电子产品),请使用以下语句:

WITH RECURSIVE category_path (id, title, parent_id) AS
(
  SELECT id, title, parent_id
    FROM category
    WHERE id = 10 -- child node
  UNION ALL
  SELECT c.id, c.title, c.parent_id
    FROM category_path AS cp JOIN category AS c
      ON cp.parent_id = c.id
)
SELECT * FROM category_path;

计算每个节点的级别

假设根节点的级别为0,则下面的每个节点的级别等于其父节点的级别加1。

WITH RECURSIVE category_path (id, title, lvl) AS
(
  SELECT id, title, 0 lvl
    FROM category
    WHERE parent_id IS NULL
  UNION ALL
  SELECT c.id, c.title,cp.lvl + 1
    FROM category_path AS cp JOIN category AS c
      ON cp.id = c.parent_id
)
SELECT * FROM category_path
ORDER BY lvl;

删除节点及其后代

要删除节点及其后代,只需删除节点本身,所有后代将被外键约束的DELETE CASCADE自动删除。

例如,要删除"笔记本电脑和PC"节点及其子节点("笔记本电脑,PC"),请使用以下语句:

DELETE FROM category 
WHERE
    id = 2;

删除节点并提升其后代

要删除非叶子节点并提升其后代:

  • 首先,将节点的直接子节点的parent_id更新为新的父节点的id。

  • 然后,删除该节点。

例如,要删除"智能手机"节点并升级其子代,例如Android,iOS,"其他智能手机"节点,请执行以下操作:

首先,为智能手机的所有直接子代更新parent_id:

UPDATE category 
SET 
    parent_id = 7 -- Phones & Accessories
WHERE
    parent_id = 5; -- Smartphones

其次,删除"智能手机"节点:

DELETE FROM category 
WHERE
    id = 8;

这两个语句都应该包装在一个事务中:

BEGIN;

UPDATE category 
SET 
    parent_id = 7 
WHERE 
    parent_id = 5;

DELETE FROM category 
WHERE 
    id = 8;

COMMIT;

移动子树

要移动子树,只需更新子树顶部节点的parent_id。
例如,要将"照相机和照片"作为"电话和附件"的子项移动,请使用以下语句:

UPDATE category 
SET 
    parent_id = 7
WHERE
    id = 5;

在本教程中,您学习了如何使用邻接表模型来管理MySQL中的分层数据。