SQL遞歸查詢知多少

最近工做中遇到了一個問題,須要根據保存的流程數據,構建流程圖。數據庫中保存的流程數據是樹形結構的,表結構及數據以下圖:html

流程表結構數據舉例

仔細觀察表結構,會發現其樹形結構的特色:算法

  • FFIRSTNODE:標記是否爲根節點
  • FSTABLENAME:標記來源單據名稱
  • FSID:標記來源單據分錄ID
  • FTTABLENAME :標記目標單據名稱
  • FTID:標記目標單據分錄ID

圖中的流程爲:
銷售合同-->銷售訂單-->發貨通知單-->銷售出庫單sql

首先想到的辦法就是把流程數據取回來,而後代碼構造流程圖。
第一個思路:根據根節點循環往下找,吭呲半天,發現沒那麼簡單。
由於任何一個源頭單據均可以屢次下推目標單據:
第二個思路:先找到終極節點,在從終極節點往上找只至根節點爲0。
這個思路實現起來也沒有那麼複雜,邏輯理清,循環遍歷,最終也能實現結果。(但在大數據量狀況下,易致使性能瓶頸。)數據庫

這一次咱們換一個思路,讓SQL來替咱們作這一複雜的遞歸查詢。express

1、SqlServer 遞歸查詢

一、基本概念

公用表表達式 (CTE) 能夠認爲是在單個 SELECT、INSERT、UPDATE、DELETE 或 CREATE VIEW 語句的執行範圍內定義的臨時結果集。公用表表達式能夠包括對自身的引用,這種表達式稱爲遞歸公用表表達式。oracle

  • 建立遞歸查詢。有關詳細信息,請參閱使用公用表表達式的遞歸查詢
  • 在不須要常規使用視圖時替換視圖,也就是說,沒必要將定義存儲在元數據中。
  • 啓用按從標量嵌套 select 語句派生的列進行分組,或者按不肯定性函數或有外部訪問的函數進行分組。
  • 在同一語句中屢次引用生成的表。

MSDN上對CTE的介紹
T-SQL查詢進階--詳解公用表表達式(CTE)函數

CTE 的基本語法結構以下:sqlserver

WITH expression_name [ ( column_name [,...n] ) ]
AS
( CTE_query_definition )
--只有在查詢定義中爲全部結果列都提供了不一樣的名稱時,列名稱列表纔是可選的。
--運行 CTE 的語句爲:
SELECT <column_list> FROM expression_name;

即三個部分:性能

  1. 公用表表達式的名字(在WITH關鍵字以後)
  2. 查詢的列名(可選)
  3. 緊跟AS以後的SELECT語句(若是AS以後有多個對公用表的查詢,則只有第一個查詢有效

二、動手實踐

根據官網示例咱們很簡單就能夠寫出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定位點類型和遞歸部分的類型不匹配

遞歸路徑查詢結果

2、Oracle 遞歸查詢

一、基本概念

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;
  • 條件1: 是根結點的限定語句,固然能夠放寬限定條件,以遍歷多個根結點,實際就是多棵樹。
  • 條件2:是鏈接條件,其中用PRIOR表示上一條記錄。
    好比CONNECT BY PRIOR Id = Parent_Id就是說上一條記錄的Id 是本條記錄的Parent_Id
  • 條件3:過濾返回的結果集。

PRIOR關鍵字

運算符PRIOR被放置於等號先後的位置,決定着查詢時的檢索順序。

  • PRIOR被置於CONNECT BY子句中等號的前面時,則強制從根節點到葉節點的順序檢索,爲自頂向下查找。
    如:CONNECT BY PRIOR Id=Parent_Id
  • PIROR運算符被置於CONNECT BY 子句中等號的後面時,則強制從葉節點到根節點的順序檢索,爲自底向上的查找。
    如: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語句

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遞歸查詢後進行篩選啊。

相關文章
相關標籤/搜索