優雅的對樹形結構進行高效能分頁,閉包表才是yyds

👉 這是一個或許對你有用的社群
🐱 一對一交流/面試小冊/簡歷最佳化/求職解惑,歡迎加入芋道快速開發平臺知識星球。下面是星球提供的部分資料:
👉這是一個或許對你有用的開源專案
國產 Star 破 10w+ 的開源專案,前端包括管理後臺 + 微信小程式,後端支援單體和微服務架構。
功能涵蓋 RBAC 許可權、SaaS 多租戶、資料許可權、商城、支付、工作流、大屏報表、微信公眾號、CRM 等等功能:
  • Boot 倉庫:https://gitee.com/zhijiantianya/ruoyi-vue-pro
  • Cloud 倉庫:https://gitee.com/zhijiantianya/yudao-cloud
  • 影片教程:https://doc.iocoder.cn
【國內首批】支援 JDK 21 + SpringBoot 3.2.2、JDK 8 + Spring Boot 2.7.18 雙版本 

一、定義的概述

閉包表(Closure Table)是一種用於儲存和查詢樹形資料結構的技術,它透過在關係表中記錄樹節點之間的直接和間接關係來表示節點之間的層次結構。閉包表的設計目的是支援高效的樹遍歷和查詢操作。
基於 Spring Boot + MyBatis Plus + Vue & Element 實現的後臺管理系統 + 使用者小程式,支援 RBAC 動態許可權、多租戶、資料許可權、工作流、三方登入、支付、簡訊、商城等功能
  • 專案地址:https://github.com/YunaiV/ruoyi-vue-pro
  • 影片教程:https://doc.iocoder.cn/video/

二、閉包表的特點

閉包表通常是一個包含兩個主要列的表:祖先列和後代列。每一行記錄都表示一對節點之間的關係,其中祖先列儲存父節點(或祖先節點)的標識,後代列儲存子節點(或後代節點)的標識。透過在閉包表中插入適當的記錄,可以建立節點之間的所有直接和間接關係。
使用閉包表可以快速地確定一個節點的所有祖先節點和後代節點。例如,要查詢一個節點的所有後代節點,只需在後代列中查詢包含該節點標識的記錄。要查詢一個節點的所有祖先節點,只需在祖先列中查詢包含該節點標識的記錄。
閉包表的結構和查詢方式使得樹的遍歷和查詢操作變得高效。它避免了在樹結構中進行遞迴或迭代遍歷的複雜性,透過簡單的資料庫查詢語句即可完成。
閉包表還具有靈活性和可擴充套件性。當需要新增、刪除或移動節點時,只需對閉包表進行相應的插入、刪除或更新操作,而無需修改樹結構本身。這種設計使得閉包表適用於動態變化的樹結構。
基於 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 實現的後臺管理系統 + 使用者小程式,支援 RBAC 動態許可權、多租戶、資料許可權、工作流、三方登入、支付、簡訊、商城等功能
  • 專案地址:https://github.com/YunaiV/yudao-cloud
  • 影片教程:https://doc.iocoder.cn/video/

三、閉包表的角色

在閉包表(Closure Table)的設計中,有幾個關鍵角色扮演重要的作用:

  • 節點(Node): 節點是樹結構中的元素或實體,可以是任何具體的物件,如組織機構中的部門、分類體系中的分類、評論樹中的評論等。每個節點在閉包表中都有唯一的識別符號。
  • 關係表(Closure Table): 關係表是儲存節點之間關係的表格。它包含兩個主要列:祖先列和後代列。每一行記錄都表示節點之間的關係,其中祖先列儲存父節點(或祖先節點)的標識,後代列儲存子節點(或後代節點)的標識。
  • 閉包(Closure): 閉包是指節點之間的直接和間接關係的集合。透過閉包表,可以透過查詢祖先列或後代列來獲取某個節點的閉包。閉包包含了節點的所有祖先節點和後代節點。
  • 祖先節點(Ancestor): 祖先節點是指一個節點的直接或間接上級節點。在閉包表中,透過查詢祖先列可以獲得一個節點的所有祖先節點。
  • 後代節點(Descendant): 後代節點是指一個節點的直接或間接下級節點。在閉包表中,透過查詢後代列可以獲得一個節點的所有後代節點。

四、一個案例演示閉包表

假設有一個組織結構的樹形選單,每個選單項代表一個部門或子部門,其中包含部門名稱和部門ID。我們可以使用閉包表來表示樹形結構中的節點關係,並實現分頁查詢。
下面是一個使用閉包表實現樹形結構資料的分頁查詢的案例。

4.1 閉包表建立

首先,我們建立部門資訊表
CREATETABLE`departments` (
`id`intNOTNULLCOMMENT'ID',
`name`varchar(50CHARACTERSET utf8mb4 COLLATE utf8mb4_general_ci DEFAULTNULLCOMMENT'部門名稱',
`parent_id`intDEFAULTNULLCOMMENT'父ID',
  PRIMARY KEY (`id`)
ENGINE=InnoDBDEFAULTCHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='部門表';
然後,我們建立閉包表
CREATETABLE`departments_closure_table` (
`ancestor`intNOTNULLCOMMENT'祖先節點',
`descendant`intNOTNULLCOMMENT'後代節點',
  PRIMARY KEY (`ancestor`,`descendant`),
KEY`fk_descendant` (`descendant`),
CONSTRAINT`fk_ancestor`FOREIGNKEY (`ancestor`REFERENCES`departments` (`id`ONDELETE RESTRICT ONUPDATE RESTRICT,
CONSTRAINT`fk_descendant`FOREIGNKEY (`descendant`REFERENCES`departments` (`id`ONDELETE RESTRICT ONUPDATE RESTRICT
ENGINE=InnoDBDEFAULTCHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='部門資訊閉包表';

4.2 閉包表資料初始化寫入

初始化部門表
INSERTINTO`hytto_cs`.`departments`(`id``name``parent_id`VALUES (1'集團總部'NULL);
INSERTINTO`hytto_cs`.`departments`(`id``name``parent_id`VALUES (2'華北總部'1);
INSERTINTO`hytto_cs`.`departments`(`id``name``parent_id`VALUES (3'華南總部'1);
INSERTINTO`hytto_cs`.`departments`(`id``name``parent_id`VALUES (4'華東總部'1);
INSERTINTO`hytto_cs`.`departments`(`id``name``parent_id`VALUES (5'華中總部'1);
INSERTINTO`hytto_cs`.`departments`(`id``name``parent_id`VALUES (6'華西總部'1);
INSERTINTO`hytto_cs`.`departments`(`id``name``parent_id`VALUES (7'北京子公司'2);
INSERTINTO`hytto_cs`.`departments`(`id``name``parent_id`VALUES (8'天津子公司'2);
INSERTINTO`hytto_cs`.`departments`(`id``name``parent_id`VALUES (9'河北子公司'2);
INSERTINTO`hytto_cs`.`departments`(`id``name``parent_id`VALUES (10'廣東子公司'3);
INSERTINTO`hytto_cs`.`departments`(`id``name``parent_id`VALUES (11'廣西子公司'3);
INSERTINTO`hytto_cs`.`departments`(`id``name``parent_id`VALUES (12'海南子公司'3);
INSERTINTO`hytto_cs`.`departments`(`id``name``parent_id`VALUES (13'四川子公司'6);
INSERTINTO`hytto_cs`.`departments`(`id``name``parent_id`VALUES (14'重慶子公司'6);
INSERTINTO`hytto_cs`.`departments`(`id``name``parent_id`VALUES (15'貴州子公司'6);
INSERTINTO`hytto_cs`.`departments`(`id``name``parent_id`VALUES (16'雲南子公司'6);
INSERTINTO`hytto_cs`.`departments`(`id``name``parent_id`VALUES (17'成都辦事處'13);
INSERTINTO`hytto_cs`.`departments`(`id``name``parent_id`VALUES (18'廣元辦事處'13);
INSERTINTO`hytto_cs`.`departments`(`id``name``parent_id`VALUES (19'雅安辦事處'13);
INSERTINTO`hytto_cs`.`departments`(`id``name``parent_id`VALUES (20'綿陽辦事處'13);
已經填充了departments表,現在我們需要填充閉包表。可以使用下面的SQL初始化(初始化的最大層次隨著實際情況變動)。
-- 初始化自身關係
INSERTINTO departments_closure_table (ancestor, descendant, depth)
SELECTidid0
FROM departments;

-- 初始化父子關係
INSERTINTO departments_closure_table (ancestor, descendant, depth)
SELECT ct.ancestor, d.id, ct.depth + 1
FROM departments_closure_table AS ct
JOIN departments AS d ON ct.descendant = d.parent_id
where ct.depth + 1 = 1;

-- 初始化爺孫關係
INSERTINTO departments_closure_table (ancestor, descendant, depth)
SELECT ct.ancestor, d.id, ct.depth + 1
FROM departments_closure_table AS ct
JOIN departments AS d ON ct.descendant = d.parent_id
where ct.depth + 1 = 2;

-- 初始化四代關係
INSERTINTO departments_closure_table (ancestor, descendant, depth)
SELECT ct.ancestor, d.id, ct.depth + 1
FROM departments_closure_table AS ct
JOIN departments AS d ON ct.descendant = d.parent_id
where ct.depth + 1 = 3;

也可以這麼初始化
INSERTINTO departments_closure_table (ancestor, descendant, depth)
WITHRECURSIVE cte AS (
SELECTidas ancestor, idas descendant, 0asdepth
FROM departments
UNIONALL
SELECT cte.ancestor, departments.id, cte.depth + 1
FROM cte
JOIN departments ON cte.descendant = departments.parent_id
)
SELECT ancestor, descendant, depth
FROM cte
WHERE ancestor != descendant;

4.3 閉包表查詢

我們可以使用閉包表來進行樹形結構的分頁查詢。假設我們想要按照部門ID升序進行分頁查詢,每頁顯示5個部門
SELECT d.*
FROM departments AS d
JOIN departments_closure_table AS ct ON d.id = ct.descendant
WHERE ct.ancestor = 1-- 根部門的ID
ORDERBY d.id
LIMIT05;

4.4 閉包表的更新

注意:下面的更新案例是全量的,實際開發中你要帶上引數,使用區域性更新;切記切記
清空現有的閉包表資料:
DELETEFROM departments_closure_table;
使用遞迴重新生成閉包表資料並插入到departments_closure_table表中:
INSERTINTO departments_closure_table (ancestor, descendant, depth)
WITHRECURSIVE cte AS (
SELECTidid0
FROM departments
UNIONALL
SELECT cte.ancestor, departments.id, cte.depth + 1
FROM cte
JOIN departments ON cte.descendant = departments.parent_id
)
SELECT ancestor, descendant, depth
FROM cte
WHERE ancestor != descendant;

五、總結

閉包表是一種用於儲存和查詢樹形資料結構的技術,透過在關係表中記錄節點之間的直接和間接關係,實現了高效的樹遍歷和查詢操作。它提供了一種簡單而靈活的方法來處理具有父子關係的層次結構資料。

歡迎加入我的知識星球,全面提升技術能力。
👉 加入方式,長按”或“掃描”下方二維碼噢
星球的內容包括:專案實戰、面試招聘、原始碼解析、學習路線。

文章有幫助的話,在看,轉發吧。
謝謝支援喲 (*^__^*)

相關文章