SQL-遞歸查詢在Ora與Mssql
今天在工作中,有同事“請教”從 Sql Server 移植數據到 DM DB 的改寫問題,本以為難度不大,結果發現 Sql Server 數據庫的語法、架構上,與 Oracle / DM 數據庫差異還是蠻大的,弄得沒怎麽用過 Sql Server 的我十分頭大,趕緊記載一些工作中遇到的問題,也加固 SQL 的學習,像遞歸查詢這個問題,對我來說一直都是一個比較生澀的點。
在 Mssql 中,遞歸的語法是用 With 來實現的,所以一開始我還以為是要用 With 生成臨時結果來處理,後來在網上查找了一下資料,才知道語法就是這樣的。可以參考 TurboWay 博客(https://www.cnblogs.com/TurboWay/p/7728746.html)中的說法,於是我在筆記本上裝了一個 Sql Server 2017 進行測試,為了方便,就采用了博客中的腳本進行測試。
/* test表 ID 地區ID Name 地區名稱 Main_ID 地區所屬上級ID Sign 地區等級 例如:福建-廈門-湖裏 分別是 1,2,3 */ -- 建表 SELECT 1003 ID, ‘福建‘ Name, 0 Main_ID, 1 Sign INTO test union all SELECT 1050 , ‘福州‘ , 1003 , 2 union all SELECT 1051 , ‘廈門‘ , 1003, 2 union ALL SELECT 1375 , ‘思明‘ , 1051 , 3 union all SELECT 1382 , ‘海滄‘ , 1051 , 3 union all SELECT 1381 , ‘湖裏‘ , 1051 , 3 union all SELECT 1374 , ‘集美‘ , 1051 , 3 union all SELECT 1373, ‘同安‘ , 1051 , 3 union all SELECT 1380 , ‘翔安‘ , 1051 , 3 union ALL SELECT 667582720122 , ‘鼓樓‘ , 1050 , 3 union all SELECT 667582725528 , ‘臺江‘ , 1050 , 3 union all SELECT 667582729587 , ‘倉山‘ , 1050 , 3 union all SELECT 667582732602 , ‘馬尾‘ , 1050 , 3 union all SELECT 667582735385 , ‘晉安‘ , 1050 , 3 union all SELECT 667582738507 , ‘閩侯‘ , 1050 , 3 union all SELECT 667582742586 , ‘連江‘ , 1050 , 3 union all SELECT 667582745634 , ‘羅源‘ , 1050 , 3 union all SELECT 667582748358 , ‘閩清‘ , 1050 , 3 union all SELECT 667582751824 , ‘永泰‘ , 1050 , 3 union all SELECT 667582755215 , ‘平潭‘ , 1050 , 3 union all SELECT 667582760309 , ‘福清‘ , 1050 , 3 union all SELECT 667582764565 , ‘長樂‘ , 1050 , 3;
這裏才發現,在 Mssql 中,可以通過這樣創建表,有點類似 Oracle 語法中的 Create Table as Select 的方式。通過導出 DDL 可以看到表 test 中列的精度。
CREATE TABLE [dbo].[test]( [ID] [numeric](12, 0) NOT NULL, [Name] [varchar](4) NOT NULL, [Main_ID] [int] NOT NULL, [Sign] [int] NOT NULL ) ON [PRIMARY] GO
發現 number 類型和 varchar 類型,都是剛好是 id 列和 name 列的最大精度。表環境擬好以後,可以執行 SQL。
WITH CTE AS ( --父項 SELECT a.*,1 level FROM test a WHERE ID=1003 UNION ALL --遞歸結果集中的下級 SELECT a.* , level + 1 as level FROM test a INNER JOIN CTE b ON b.ID=a.Main_ID )select * from CTE; --結果集 ID Name Main_ID Sign level --------------------------------------- ---- ----------- ----------- ----------- 1003 福建 0 1 1 1050 福州 1003 2 2 1051 廈門 1003 2 2 1375 思明 1051 3 3 1382 海滄 1051 3 3 1381 湖裏 1051 3 3 1374 集美 1051 3 3 1373 同安 1051 3 3 1380 翔安 1051 3 3 667582720122 鼓樓 1050 3 3 667582725528 臺江 1050 3 3 667582729587 倉山 1050 3 3 667582732602 馬尾 1050 3 3 667582735385 晉安 1050 3 3 667582738507 閩侯 1050 3 3 667582742586 連江 1050 3 3 667582745634 羅源 1050 3 3 667582748358 閩清 1050 3 3 667582751824 永泰 1050 3 3 667582755215 平潭 1050 3 3 667582760309 福清 1050 3 3 667582764565 長樂 1050 3 3
其實,從這個結果集就很好理解遞歸查詢應用場景,以地點這個場景來解釋層次查詢是非常適合的,如果表中沒有 Sign 這個列,在 Sql 中的 level 也能非常好的分層。第一層就是福建,福建下面的第二層地點就是福州和廈門, Sign = 3 的就是第三層地名。把它們關聯起來的是Id 與 Main_id ,一般我們把 Main_id 叫 Parent id ,即它的父節點 id 。比如地點馬尾,它的 Main_id 是1050,而 id 是1050的就是馬尾的父節點——福州。如果我們把整個結果集當成一棵樹的結構的話,在以上 Sql 中,是從 id = 1003 開始,遍歷整棵樹,Sign = 1 是根的話,Sign = 3 的就是這棵樹的葉,這樣的查詢就是自頂向下(樹形結構是倒著的)的一種層次查詢。(僅個人理解,可能不準確)
而下面這樣,就是自底向上的查詢了,通過一個葉子節點去查找父節點。也很好理解,第一部分先獲取到葉子節點的 id,第二部分,通過已經查到的葉子節點數據(cte as b)的父節點,來找父節點的數據(b.Main_id = test.id)。
with cte as ( select a.*,1 as level from test a where id = 667582751824 union all select a.*,level + 1 as level from test a inner join cte as b on a.id = b.Main_ID ) select * from cte; --結果集 ID Name Main_ID Sign level --------------------------------------- ---- ----------- ----------- ----------- 667582751824 永泰 1050 3 1 1050 福州 1003 2 2 1003 福建 0 1 3
在 Oracle 語法中,層次查詢的語法與 Mssql 中不一樣,以前在用到的時候,一直難以理解是怎麽個關聯關系的,直到在一個博客中看到一種說法:prior 就是"找"的意思,prior id 就是找葉子節點,自頂而下查詢;prior parent_id 就是找父節點,即自底向上查詢。這樣解釋起來就非常有意思而且便於記憶,是個非常棒的點。以下是 Oracle / DM 對應的SQL。
select * from test start with id=1003 connect by prior id= main_id; select a.*,level from test a start with id = 667582735385 connect by prior main_id = id;
SQL-遞歸查詢在Ora與Mssql