SQL遞歸查詢實戰 WITH子句實現樹形結構遍歷

sql遞歸查詢通過with recursive子句實現樹形結構遍歷,有效解決傳統join難以處理的動態層級數據問題。1. with recursive由錨點成員和遞歸成員組成,通過union all連接,前者定義初始查詢條件,如從特定節點(如alice)開始;2. 后者不斷迭代查找下級節點,直到無新記錄生成為止;3. 此方法適用于組織架構、產品物料清單、評論嵌套、文件系統、供應鏈追溯等多種場景,能靈活應對未知層級深度的數據關系,避免冗長join語句,提升查詢效率與可維護性。

SQL遞歸查詢實戰 WITH子句實現樹形結構遍歷

SQL遞歸查詢,尤其是借助WITH子句(Common table Expressions, CTEs)來實現樹形結構遍歷,簡單來說,就是一種在關系型數據庫中優雅地處理層級數據的強大工具。它能讓你輕松地向上追溯(比如找老板的老板)或向下展開(比如找下屬的下屬),而不需要寫一復雜的嵌套查詢或多次連接。它就像是給數據庫裝上了“層級導航”功能,讓那些原本看起來復雜得一塌糊涂的父子關系,變得清晰可見。

SQL遞歸查詢實戰 WITH子句實現樹形結構遍歷

解決方案

要實現樹形結構遍歷,我們通常會用到WITH RECURSIVE(或者某些數據庫中是WITH加上特定的語法,如SQL Server的WITH … AS (ANCHOR UNION ALL RECURSIVE))。其核心思想是將一個查詢分成兩部分:一個“錨點成員”(Anchor Member)和一個“遞歸成員”(Recursive Member),然后用UNION ALL把它們連接起來。

舉個最常見的例子:一個員工表,里面有員工ID、姓名和上級ID。

SQL遞歸查詢實戰 WITH子句實現樹形結構遍歷

假設我們有這樣的表結構和數據:

CREATE TABLE employees (     id INT PRIMARY KEY,     name VARCHAR(50),     manager_id INT,     FOREIGN KEY (manager_id) REFERENCES employees(id) );  INSERT INTO employees (id, name, manager_id) VALUES (1, 'Alice', NULL),   -- CEO (2, 'Bob', 1), (3, 'Charlie', 1), (4, 'David', 2), (5, 'Eve', 2), (6, 'Frank', 3), (7, 'Grace', 4);

現在,我們想找出所有直接或間接向Alice匯報的員工:

SQL遞歸查詢實戰 WITH子句實現樹形結構遍歷

WITH RECURSIVE Subordinates AS (     -- 錨點成員:從Alice開始     select         id,         name,         manager_id,         1 AS level -- 標記層級,Alice是第1級     FROM         employees     WHERE         name = 'Alice'      UNION ALL      -- 遞歸成員:找到上一級結果的下屬     SELECT         e.id,         e.name,         e.manager_id,         s.level + 1 AS level -- 層級加1     FROM         employees e     INNER JOIN         Subordinates s ON e.manager_id = s.id ) SELECT     id,     name,     manager_id,     level FROM     Subordinates ORDER BY     level, id;

這段代碼首先找到了Alice(錨點),然后不斷地查找那些以上一層查詢結果中的員工為manager_id的員工,直到沒有新的下屬被找到為止。level字段在這里非常有用,可以清晰地展示出每個員工在組織架構中的深度。

為什么傳統的JOIN操作難以應對樹形結構?

說實話,我第一次接觸到這種需求時,下意識也想用JOIN來解決。畢竟,關系型數據庫的核心就是JOIN嘛。但很快就會發現,對于深度不確定的樹形結構,傳統的JOIN操作會變得異常笨拙,甚至可以說束手無策。

你想啊,如果我想找到Alice的所有下屬,包括下屬的下屬,以及下屬的下屬的下屬……如果我只知道組織架構最多有三層,那我可能還能寫出三個LEFT JOIN或者INNER JOIN。但萬一組織架構有十層呢?或者,更要命的是,我根本不知道它到底有多少層?你總不能寫十個甚至更多個JOIN吧?那樣寫出來的sql語句會非常冗長、難以閱讀和維護,而且性能也會是個大問題。每增加一層深度,你就得增加一個JOIN,這根本不符合“一次編寫,通用執行”的編程原則。這種“預知深度”的限制,讓傳統JOIN在處理這類問題時顯得力不從心。它更適合處理固定、明確的關系,而不是這種動態、可變深度的層級關系。

WITH RECURSIVE子句的工作原理與關鍵組成部分

WITH RECURSIVE子句,或者說遞歸CTE,它能優雅地解決傳統JOIN的困境,這玩意兒的工作原理其實有點像我們編程里的遞歸函數調用。它主要由兩部分組成,缺一不可:

  1. 錨點成員(Anchor Member): 這是遞歸的“起點”或“基礎案例”。它是一個非遞歸的SELECT語句,用于生成遞歸的初始行集。在上面員工的例子中,就是SELECT … FROM employees WHERE name = ‘Alice’這部分。它定義了我們從哪里開始遍歷樹。如果沒有錨點,遞歸就無從談起。

  2. 遞歸成員(Recursive Member): 這是遞歸的“迭代步驟”。它是一個SELECT語句,必須引用CTE本身(也就是Subordinates這個名字)。每次執行時,它會使用前一次迭代(包括錨點成員的第一次迭代)的結果集作為輸入,然后生成新的行集。在我們的例子中,SELECT e.id, … FROM employees e INNER JOIN Subordinates s ON e.manager_id = s.id就是遞歸成員。它不斷地從前一輪的結果中找出新的相關數據。

這兩部分通過UNION ALL連接起來。數據庫系統會先執行錨點成員,得到第一批結果。然后,它會將這批結果傳遞給遞歸成員,遞歸成員處理后生成新的結果集。接著,這個新的結果集又會被傳回給遞歸成員,如此反復,直到遞歸成員不再產生新的行為止。這就是遞歸的“終止條件”——當某次迭代的結果集為空時,整個遞歸過程就停止了。

值得注意的是,使用UNION ALL而不是UNION通常是更優的選擇,因為它避免了去重操作,在大多數遞歸場景下,我們通常不關心中間結果的重復,而且UNION ALL的性能會更好。當然,如果你的數據中存在循環引用(比如A是B的上級,B又是A的上級),那么遞歸查詢可能會陷入無限循環。一些數據庫提供了額外的機制(如SQL Server的MAXRECURSION選項,或者postgresql/oracle的CYCLE子句)來檢測和處理這種情況,防止資源耗盡。

實際應用場景:除了組織架構,還能用在哪里?

WITH RECURSIVE的魅力遠不止于組織架構圖。它的應用范圍非常廣泛,只要數據存在層級關系,它就能派上用場。我個人覺得,它簡直是處理各種“父子孫”關系的利器。

  1. 產品物料清單(Bill of Materials, bom): 想象一個復雜的產品,它由多個子部件組成,而每個子部件又可能由更小的零件構成。遞歸查詢可以輕松地展開整個物料清單,計算每個最終產品的總零件數,或者找出某個特定零件被哪些產品直接或間接使用。這對于生產計劃和成本核算來說簡直是福音。

  2. 評論/論壇帖子嵌套: 很多論壇或博客的評論系統都支持回復功能,形成多級嵌套的評論串。使用遞歸查詢,你可以輕松地將這些評論按照層級關系展示出來,甚至可以限制顯示深度,或者找出某個評論的所有子評論。

  3. 文件系統結構: 數據庫中存儲的文件和目錄信息,天然就是一種樹形結構。遞歸查詢可以用來模擬ls -R或dir /s命令,列出某個目錄下所有子目錄和文件,或者找出特定類型的文件。

  4. 供應鏈追溯: 在復雜的供應鏈中,產品從原材料到最終消費者可能經過多個環節。遞歸查詢可以幫助你追溯某個批次產品的上游供應商,或者下游銷售渠道,這對于質量控制和召回管理至關重要。

  5. 網絡圖/路徑查找(簡化版): 雖然專業的圖數據庫更適合復雜的圖算法,但在關系型數據庫中,對于簡單的節點間連接(比如朋友關系,或者城市間航線),遞歸查詢可以用來找出兩個節點之間的所有可能路徑,或者某個節點可達的所有節點。當然,這通常需要一些額外的技巧來避免循環和記錄路徑。

可以說,任何你看到數據呈現出“包含”、“屬于”、“是…的子集”這種層級關系的場景,WITH RECURSIVE都值得你考慮。它讓數據庫在處理這類問題時,變得異常靈活和強大。

? 版權聲明
THE END
喜歡就支持一下吧
點贊9 分享