1. 程式人生 > >SQL-遞歸查詢在Ora與Mssql

SQL-遞歸查詢在Ora與Mssql

creat st表 有意 https test strong eat bold 遇到的問題

今天在工作中,有同事“請教”從 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