最近工做中遇到了一個問題,須要根據保存的流程數據,構建流程圖。數據庫中保存的流程數據是樹形結構的,表結構及數據以下圖:html
仔細觀察表結構,會發現其樹形結構的特色:算法
圖中的流程爲:
銷售合同-->銷售訂單-->發貨通知單-->銷售出庫單sql
首先想到的辦法就是把流程數據取回來,而後代碼構造流程圖。
第一個思路:根據根節點循環往下找,吭呲半天,發現沒那麼簡單。
由於任何一個源頭單據均可以屢次下推目標單據:
第二個思路:先找到終極節點,在從終極節點往上找只至根節點爲0。
這個思路實現起來也沒有那麼複雜,邏輯理清,循環遍歷,最終也能實現結果。(但在大數據量狀況下,易致使性能瓶頸。)數據庫
這一次咱們換一個思路,讓SQL來替咱們作這一複雜的遞歸查詢。express
公用表表達式 (CTE) 能夠認爲是在單個 SELECT、INSERT、UPDATE、DELETE 或 CREATE VIEW 語句的執行範圍內定義的臨時結果集。公用表表達式能夠包括對自身的引用,這種表達式稱爲遞歸公用表表達式。oracle
MSDN上對CTE的介紹
T-SQL查詢進階--詳解公用表表達式(CTE)函數
CTE 的基本語法結構以下:sqlserver
WITH expression_name [ ( column_name [,...n] ) ] AS ( CTE_query_definition ) --只有在查詢定義中爲全部結果列都提供了不一樣的名稱時,列名稱列表纔是可選的。 --運行 CTE 的語句爲: SELECT <column_list> FROM expression_name;
即三個部分:性能
根據官網示例咱們很簡單就能夠寫出CTE語句應用於咱們的應用場景:大數據
WITH TEST_CTE AS ( SELECT TBIE.FSTABLENAME,TBIE.FSID,TBIE.FTTABLENAME,TBIE.FTID,TBIE.FROUTEID FROM T_BF_INSTANCEENTRY TBIE WHERE TBIE.FTTABLENAME = 'T_SAL_ORDERENTRY' AND TBIE.FTID = 121625 UNION ALL SELECT CTBIE.FSTABLENAME,CTBIE.FSID,CTBIE.FTTABLENAME,CTBIE.FTID,CTBIE.FROUTEID FROM T_BF_INSTANCEENTRY CTBIE INNER JOIN TEST_CTE CTE ON CTBIE.FSID=CTE.FTID AND CTBIE.FSTABLENAME = CTE.FTTABLENAME ) SELECT * FROM TEST_CTE --限制遞歸次數 OPTION(MAXRECURSION 10)
在查詢中咱們指定條件參數WHERE TBIE.FTTABLENAME = 'T_SAL_ORDERENTRY' AND TBIE.FTID = 121625
,便可查詢到指定節點的完整流程數據。
其中在與公用表TEST_CTE
進行關聯時,我指定了兩個條件CTBIE.FSID=CTE.FTID AND CTBIE.FSTABLENAME = CTE.FTTABLENAME
,由於不一樣類型的單據各有一套自增的ID,直接用ID進行關聯迭代不可行。
須要注意的是OPTION(MAXRECURSION 10)
是用來限制遞歸次數,以免無限遞歸致使數據庫性能消耗嚴重。
WITH TEST_CTE AS ( SELECT TBIE.FSTABLENAME,TBIE.FSID,TBIE.FTTABLENAME,TBIE.FTID,TBIE.FROUTEID,Cast(TBIE.FTID as nvarchar(4000)) AS PATH FROM T_BF_INSTANCEENTRY TBIE WHERE TBIE.FTTABLENAME = 'T_SAL_ORDERENTRY' AND TBIE.FTID = 121625 UNION ALL SELECT CTBIE.FSTABLENAME,CTBIE.FSID,CTBIE.FTTABLENAME,CTBIE.FTID,CTBIE.FROUTEID,CTE.PATH+'->'+Cast(CTBIE.FTID as nvarchar(4000)) PATH FROM T_BF_INSTANCEENTRY CTBIE INNER JOIN TEST_CTE CTE ON CTBIE.FSID=CTE.FTID AND CTBIE.FSTABLENAME = CTE.FTTABLENAME ) SELECT * FROM TEST_CTE --限制遞歸次數 OPTION(MAXRECURSION 10)
基於上一個查詢,增長一列手動拼接遞歸路徑。注意sql中將PATH設置的類型爲navarchar(4000),在union中,兩邊的表結構類型必須保持一致,不然會報錯定位點類型和遞歸部分的類型不匹配
。可參考此篇博文
解決CTE定位點類型和遞歸部分的類型不匹配。
Oracle中的遞歸查詢語句爲start with…connect by prior
,爲中序遍歷算法。
可參考Oracle 樹操做、遞歸查詢(select…start with…connect by…prior)瞭解更多。
其基本語法是:
select colname from tablename start with 條件1 connect by 條件2 where 條件3;
CONNECT BY PRIOR Id = Parent_Id
就是說上一條記錄的Id 是本條記錄的Parent_Id。PRIOR關鍵字
運算符PRIOR被放置於等號先後的位置,決定着查詢時的檢索順序。
CONNECT BY PRIOR Id=Parent_Id
CONNECT BY Id=PRIOR Parent_Id
PS:當CONNECT BY後指定多個鏈接條件時,每一個條件都應指定PRIOR
關鍵字
理清了用法,咱們用Oracle來對查詢一下業務流程。
SELECT * FROM T_BF_INSTANCEENTRY START WITH (FTID=100501 AND FTTABLENAME = 'T_SAL_ORDERENTRY') CONNECT BY FSID= PRIOR FTID AND FSTABLENAME =PRIOR FTTABLENAME
該流程爲:銷售訂單-->發貨通知單-->銷售出庫單-->退貨通知單-->銷售退貨單
其中在指定鏈接條件時,我指定了兩個條件FSID= PRIOR FTID AND FSTABLENAME =PRIOR FTTABLENAME
,由於不一樣類型的單據各有一套自增的ID,直接用ID進行關聯迭代不可行。
Oracle中提供了SYS_CONNECT_BY_PATH
函數用來進行鏈接路徑。
SELECT TBIE.*, SUBSTR(SYS_CONNECT_BY_PATH(FTID,'->'),3) NAME_PATH FROM T_BF_INSTANCEENTRY TBIE START WITH (FTID=100501 AND FTTABLENAME = 'T_SAL_ORDERENTRY') CONNECT BY FSID= PRIOR FTID AND FSTABLENAME =PRIOR FTTABLENAME
基於上個查詢,增長了一列SUBSTR(SYS_CONNECT_BY_PATH(FTID,'->'),3) NAME_PATH
用來拼接遞歸路徑。
這個時候咱們要用到connect_by_root
函數,用來記錄當前節點的根節點信息。
SELECT TBIE.*, SUBSTR(SYS_CONNECT_BY_PATH(FTID,'->'),3)NAME_PATH, (connect_by_root FTID) ROOT FROM T_BF_INSTANCEENTRY TBIE START WITH (FTID=100501 AND FTTABLENAME = 'T_SAL_ORDERENTRY') CONNECT BY FSID= PRIOR FTID AND FSTABLENAME =PRIOR FTTABLENAME
Oracle也有with..as 查詢語法,通常用來進行子查詢,提升查詢效率。
語法:
with tempTableName as ( select * from table1 ) select * from tempTableName
拿咱們的案例舉例就是:
with flow_temp as ( SELECT TBIE.*, SUBSTR(SYS_CONNECT_BY_PATH(FTID,'->'),3)NAME_PATH, (connect_by_root FTID) ROOT FROM T_BF_INSTANCEENTRY TBIE START WITH (FTID=100501 AND FTTABLENAME = 'T_SAL_ORDERENTRY') CONNECT BY FSID= PRIOR FTID AND FSTABLENAME =PRIOR FTTABLENAME ) select * from flow_temp
爲啥要講這個呢,咱們能夠在oracle遞歸查詢後進行篩選啊。