SQL研究 - CTE深入

本文深入研究如何使用CTE完成圖的深度遍歷,並查找最短路徑。本文假設你在爲一家航空公司做一個導遊的軟件,其中的一個基本需求是查詢從一個城市到另一個城市的飛行路線。並假設除了軟件所已知的航班之外,並不存在其他的直達航班。首先你讓用戶看到這樣的圖片:

 

在後臺你需要創建的表結構可以簡化如下:

CREATE TABLE dbo.Flights
(
  city1       NVARCHAR(256) NOT NULL ,
  city2       NVARCHAR(256) NOT NULL ,
  distance   INT NOT NULL,
  PRIMARY KEY(city1, city2),
  CHECK(city1 < city2),
  CHECK(distance > 0)
);

表中存放如下內容:

BeiJing TaiYuan 10
Beijing ZhenZhou 15
TaiYuan XiAn 12
TaiYuan YingChuan 14
TaiYuan ZhenZhou 8
WuHan XiAn 13
WuHan ZhenZhou 10
XiAn YingChuan 13
XiAn ZhenZhou 8

 

那麼你現在希望找到各個城市間所有的飛行途徑以及相應的路程。爲此我們需要增加1個新的列:

- Path nvarchar(1024) 存放所有經過的城市;

 

我們不再需要增加Distance列,因爲新增加的行自動包含原有的Distance列。

好吧,讓我來看SQL吧。

 with AllFlights as
(
select city1,city2,distance from flights
union
select city2,city1,distance from flights
),
CompleteFlights as
(
select city1,city2,distance,cast(city1+'.' as nvarchar(1024)) as [path] from AllFlights

union all

select

   a.city1,

   b.city2,

   a.distance+b.distance,

   cast([path]+a.city2+'.' as nvarchar(1024))
from  CompleteFlights a join AllFlights b
 on a.city2 = b.city1 and charindex(b.city1,[path])=0 and charindex(b.city2,[path])=0
)
select * from CompleteFlights

 

直接打印出所有結果是不明智,它不能幫助我們理解發生的一切。讓我們還是從SQL本身入手來理解它。

首先,我們使用了兩個CTE。第一個CTE是源表的擴張,既然你可以從西安到銀川,自然也能從銀川到西安。可是源表爲了減少冗餘,並沒有銀川到西安的路徑。

 

接下來是正宗的CTE了。請回憶我的上一篇文章講的CTE的結構: 錨點,連接詞,遞歸生成語句。

現在 錨點 就是第一步產生的CTE,它包含所有可以一步到達的航班信息。

連接詞是UnionAll,因爲我們要吧所有的路徑組合起來。

遞歸語句是我們這個解法的核心。

首先,仍然是 i 輪結果產生 i+1 輪的結果。i 輪的結果,自然是 源 和 目的 城市間轉機次數爲 i 的記錄。 特殊的,錨點的結果是轉機次數爲0的路徑記錄。

請注意到當轉機次數增加1時,只是從原始路徑AllFlights中選擇,並且有兩個額外的限制條件: charindex(b.city2,[path])=0

對新加入的路徑如果終點出現在路徑中,加入該邊後必然構成一個環,那不是一個正常的結果。 我們可以推斷,B.City1必然不在路徑中。

 

當新的路徑形成後,我們相應的修改Distance和Path變量。

 

好吧,讓我們看看結果吧:爲方便,我們把之前的SQL作爲一個View, FlightView

 

請找出北京到太原的所有路徑:select * from FlightView where city1='BeiJing' and city2='TaiYuan'

下面是結果:

BeiJing TaiYuan 10 BeiJing.
Beijing TaiYuan 23 Beijing.ZhenZhou.
Beijing TaiYuan 35 Beijing.ZhenZhou.XiAn.
Beijing TaiYuan 50 Beijing.ZhenZhou.XiAn.YingChuan.
Beijing TaiYuan 50 Beijing.ZhenZhou.WuHan.XiAn.
Beijing TaiYuan 65 Beijing.ZhenZhou.WuHan.XiAn.YingChuan.

 

請找出北京到西安的最短路徑:

select city1,city2,distance,[path]

from CompleteFlights
where city1='BeiJing' and city2='xian' and
         distance = (select min(distance) from CompleteFlights where city1='BeiJing' and city2='xian')

結果如下:

BeiJing XiAn 22 BeiJing.TaiYuan.

 從圖上看,這顯然是正確的結果。

 

發佈了36 篇原創文章 · 獲贊 0 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章