在SQL Server中實(shí)現(xiàn)最短路徑搜索的解決方法_Mssql數(shù)據(jù)庫(kù)教程
推薦:Sql學(xué)習(xí)第一天——SQL 練習(xí)題(建表/sql語(yǔ)句)來(lái)自Madrid且訂單數(shù)少于3的消費(fèi)者,針對(duì)這個(gè)要求作出以下:建表做題分析以及sql語(yǔ)句的寫法,感興趣的朋友可以參考下哈,希望可以幫助到你
開始
這是去年的問(wèn)題了,今天在整理郵件的時(shí)候才發(fā)現(xiàn)這個(gè)問(wèn)題,感覺(jué)頂有意思的,特記錄下來(lái)。
在表RelationGraph中,有三個(gè)字段(ID,Node,RelatedNode),其中Node和RelatedNode兩個(gè)字段描述兩個(gè)節(jié)點(diǎn)的連接關(guān)系;現(xiàn)在要求,找出從節(jié)點(diǎn)"p"至節(jié)點(diǎn)"j",最短路徑(即經(jīng)過(guò)的節(jié)點(diǎn)最少)。

圖1.
解析:
了能夠更好的描述表RelationGraph中字段Node和 RelatedNode的關(guān)系,我在這里特意使用一個(gè)圖形來(lái)描述,
如圖2.

圖2.
在圖2,可清晰的看出各個(gè)節(jié)點(diǎn)直接如何相連,也可以清楚的看出節(jié)點(diǎn)"p"至節(jié)點(diǎn)"j"的的幾種可能路徑。

從上面可以看出第2種可能路徑,經(jīng)過(guò)的節(jié)點(diǎn)最少。
為了解決開始的問(wèn)題,我參考了兩種方法,
第1方法是,
參考單源最短路徑算法:Dijkstra(迪杰斯特拉)算法,主要特點(diǎn)是以起始點(diǎn)為中心向外層層擴(kuò)展,直到擴(kuò)展到終點(diǎn)為止。

圖3.
第2方法是,
針對(duì)第1種方法的改進(jìn),就是采用多源點(diǎn)方法,這里就是以節(jié)點(diǎn)"p"和節(jié)點(diǎn)"j"為中心向外層擴(kuò)展,直到兩圓外切點(diǎn),如圖4. :

圖4.
實(shí)現(xiàn):
在接下來(lái),我就描述在SQL Server中,如何實(shí)現(xiàn)。當(dāng)然我這里采用的前面說(shuō)的第2種方法,以"P"和"J"為始點(diǎn)像中心外層層擴(kuò)展。
這里提供有表RelactionGraph的create& Insert數(shù)據(jù)的腳本:
use TestDB
go
if object_id('RelactionGraph') Is not null drop table RelactionGraph
create table RelactionGraph(ID int identity,Item nvarchar(50),RelactionItem nvarchar(20),constraint PK_RelactionGraph primary key(ID))
go
create nonclustered index IX_RelactionGraph_Item on RelactionGraph(Item) include(RelactionItem)
create nonclustered index IX_RelactionGraph_RelactionItem on RelactionGraph(RelactionItem) include(Item)
go
insert into RelactionGraph (Item, RelactionItem ) values
('a','b'),('a','c'),('a','d'),('a','e'),
('b','f'),('b','g'),('b','h'),
('c','i'),('c','j'),
('f','k'),('f','l'),
('k','o'),('k','p'),
('o','i'),('o','l')
go
編寫一個(gè)存儲(chǔ)過(guò)程up_GetPath
use TestDB
go
--Procedure:
if object_id('up_GetPath') Is not null
Drop proc up_GetPath
go
create proc up_GetPath
(
@Node nvarchar(50),
@RelatedNode nvarchar(50)
)
As
set nocount on
declare
@level smallint =1, --當(dāng)前搜索的深度
@MaxLevel smallint=100, --最大可搜索深度
@Node_WhileFlag bit=1, --以@Node作為中心進(jìn)行搜索時(shí)候,作為能否循環(huán)搜索的標(biāo)記
@RelatedNode_WhileFlag bit=1 --以@RelatedNode作為中心進(jìn)行搜索時(shí)候,作為能否循環(huán)搜索的標(biāo)記
--如果直接找到兩個(gè)Node存在直接關(guān)系就直接返回
if Exists(select 1 from RelationGraph where (Node=@Node And RelatedNode=@RelatedNode) or (Node=@RelatedNode And RelatedNode=@Node) ) or @Node=@RelatedNode
begin
select convert(nvarchar(2000),@Node + ' --> '+ @RelatedNode) As RelationGraphPath,convert(smallint,0) As StopCount
return
end
--
if object_id('tempdb..#1') Is not null Drop Table #1 --臨時(shí)表#1,存儲(chǔ)的是以@Node作為中心向外擴(kuò)展的各節(jié)點(diǎn)數(shù)據(jù)
if object_id('tempdb..#2') Is not null Drop Table #2 --臨時(shí)表#2,存儲(chǔ)的是以@RelatedNode作為中心向外擴(kuò)展的各節(jié)點(diǎn)數(shù)據(jù)
create table #1(
Node nvarchar(50),--相對(duì)源點(diǎn)
RelatedNode nvarchar(50), --相對(duì)目標(biāo)
Level smallint --深度
)
create table #2(Node nvarchar(50),RelatedNode nvarchar(50),Level smallint)
insert into #1 ( Node, RelatedNode, Level )
select Node, RelatedNode, @level from RelationGraph a where a.Node =@Node union --正向:以@Node作為源查詢
select RelatedNode, Node, @level from RelationGraph a where a.RelatedNode = @Node --反向:以@Node作為目標(biāo)進(jìn)行查詢
set @Node_WhileFlag=sign(@@rowcount)
insert into #2 ( Node, RelatedNode, Level )
select Node, RelatedNode, @level from RelationGraph a where a.Node =@RelatedNode union --正向:以@RelatedNode作為源查詢
select RelatedNode, Node, @level from RelationGraph a where a.RelatedNode = @RelatedNode --反向:以@RelatedNode作為目標(biāo)進(jìn)行查詢
set @RelatedNode_WhileFlag=sign(@@rowcount)
--如果在表RelationGraph中找不到@Node 或 @RelatedNode 數(shù)據(jù),就直接跳過(guò)后面的While過(guò)程
if not exists(select 1 from #1) or not exists(select 1 from #2)
begin
goto While_Out
end
while not exists(select 1 from #1 a inner join #2 b on b.RelatedNode=a.RelatedNode) --判斷是否出現(xiàn)切點(diǎn)
and (@Node_WhileFlag|@RelatedNode_WhileFlag)>0 --判斷是否能搜索
And @level<@MaxLevel --控制深度
begin
if @Node_WhileFlag >0
begin
insert into #1 ( Node, RelatedNode, Level )
--正向
select a.Node,a.RelatedNode,@level+1
From RelationGraph a
where exists(select 1 from #1 where RelatedNode=a.Node And Level=@level) And
Not exists(select 1 from #1 where Node=a.Node)
union
--反向
select a.RelatedNode,a.Node,@level+1
From RelationGraph a
where exists(select 1 from #1 where RelatedNode=a.RelatedNode And Level=@level) And
Not exists(select 1 from #1 where Node=a.RelatedNode)
set @Node_WhileFlag=sign(@@rowcount)
end
if @RelatedNode_WhileFlag >0
begin
insert into #2 ( Node, RelatedNode, Level )
--正向
select a.Node,a.RelatedNode,@level+1
From RelationGraph a
where exists(select 1 from #2 where RelatedNode=a.Node And Level=@level) And
Not exists(select 1 from #2 where Node=a.Node)
union
--反向
select a.RelatedNode,a.Node,@level+1
From RelationGraph a
where exists(select 1 from #2 where RelatedNode=a.RelatedNode And Level=@level) And
Not exists(select 1 from #2 where Node=a.RelatedNode)
set @RelatedNode_WhileFlag=sign(@@rowcount)
end
select @level+=1
end
While_Out:
--下面是構(gòu)造返回的結(jié)果路徑
if object_id('tempdb..#Path1') Is not null Drop Table #Path1
if object_id('tempdb..#Path2') Is not null Drop Table #Path2
;with cte_path1 As
(
select a.Node,a.RelatedNode,Level,convert(nvarchar(2000),a.Node+' -> '+a.RelatedNode) As RelationGraphPath,Convert(smallint,1) As PathLevel From #1 a where exists(select 1 from #2 where RelatedNode=a.RelatedNode)
union all
select b.Node,a.RelatedNode,b.Level,convert(nvarchar(2000),b.Node+' -> '+a.RelationGraphPath) As RelationGraphPath ,Convert(smallint,a.PathLevel+1) As PathLevel
from cte_path1 a
inner join #1 b on b.RelatedNode=a.Node
and b.Level=a.Level-1
)
select * Into #Path1 from cte_path1
;with cte_path2 As
(
select a.Node,a.RelatedNode,Level,convert(nvarchar(2000),a.Node) As RelationGraphPath,Convert(smallint,1) As PathLevel From #2 a where exists(select 1 from #1 where RelatedNode=a.RelatedNode)
union all
select b.Node,a.RelatedNode,b.Level,convert(nvarchar(2000),a.RelationGraphPath+' -> '+b.Node) As RelationGraphPath ,Convert(smallint,a.PathLevel+1)
from cte_path2 a
inner join #2 b on b.RelatedNode=a.Node
and b.Level=a.Level-1
)
select * Into #Path2 from cte_path2
;with cte_result As
(
select a.RelationGraphPath+' -> '+b.RelationGraphPath As RelationGraphPath,a.PathLevel+b.PathLevel -1 As StopCount,rank() over(order by a.PathLevel+b.PathLevel) As Result_row
From #Path1 a
inner join #Path2 b on b.RelatedNode=a.RelatedNode
and b.Level=1
where a.Level=1
)
select distinct RelationGraphPath,StopCount From cte_result where Result_row=1
go
上面的存儲(chǔ)過(guò)程,主要分為兩大部分,第1部分是實(shí)現(xiàn)如何搜索,第2部分實(shí)現(xiàn)如何構(gòu)造返回結(jié)果。其中第1部分的代碼根據(jù)前面的方法2,通過(guò)@Node 和 @RelatedNode 兩個(gè)節(jié)點(diǎn)向外層搜索,每次搜索返回的節(jié)點(diǎn)都保存至臨時(shí)表#1和#2,再判斷臨時(shí)表#1和#2有沒(méi)有出現(xiàn)切點(diǎn),如果出現(xiàn)就說(shuō)明已找到最短的路徑(經(jīng)過(guò)多節(jié)點(diǎn)數(shù)最少),否則就繼續(xù)循環(huán)搜索,直到循環(huán)至最大的搜索深度(@MaxLevel smallint=100)或找到切點(diǎn)。要是到100層都沒(méi)搜索到切點(diǎn),將放棄搜索。這里使用最大可搜索深度@MaxLevel,目的是控制由于數(shù)據(jù)量大可能會(huì)導(dǎo)致性能差,因?yàn)樵谶@里數(shù)據(jù)量與搜索性能成反比。代碼中還說(shuō)到一個(gè)正向和反向搜索,主要是相對(duì)Node 和 RelatedNode來(lái)說(shuō),它們兩者互為參照對(duì)象,進(jìn)行向外搜索使用。
下面是存儲(chǔ)過(guò)程的執(zhí)行:
use TestDB
go
exec dbo.up_GetPath
@Node = 'p',
@RelatedNode = 'j'
go

你可以根據(jù)需要來(lái),賦予@Node 和 @RelatedNode不同的值。
拓展:
前面的例子,可擴(kuò)展至城市的公交路線,提供兩個(gè)站點(diǎn),搜索經(jīng)過(guò)這兩個(gè)站點(diǎn)最少站點(diǎn)公交路線;可以擴(kuò)展至社區(qū)的人際關(guān)系的搜索,如一個(gè)人與另一個(gè)人想認(rèn)識(shí),那么他們直接要經(jīng)過(guò)多少個(gè)人才可以。除了人與人直接有直接的朋友、親戚關(guān)聯(lián),還可以通過(guò)人與物有關(guān)聯(lián)找到人與人關(guān)聯(lián),如幾個(gè)作家通過(guò)出版一個(gè)本,那么就說(shuō)明這幾個(gè)人可以通過(guò)某一本書的作者列表中找到他們存在共同出版書籍的關(guān)聯(lián),這為搜索兩個(gè)人認(rèn)識(shí)路徑提供參考。這問(wèn)題可能會(huì)非常大復(fù)雜,但可以這樣的擴(kuò)展。
小結(jié):
這里只是找兩個(gè)節(jié)點(diǎn)的所有路徑中,節(jié)點(diǎn)數(shù)最少的路徑,在實(shí)際的應(yīng)用中,可能會(huì)碰到比這里更復(fù)雜的情況。在其他的環(huán)境或場(chǎng)景可能會(huì)帶有長(zhǎng)度,時(shí)間,多節(jié)點(diǎn),多作用域等一些信息。無(wú)論如何,一般都要參考一些原理,算法來(lái)實(shí)現(xiàn)。
分享:Sql學(xué)習(xí)第三天——SQL 關(guān)于with ties介紹with ties一般是和Top , order by相結(jié)合使用的,會(huì)查詢出最后一條數(shù)據(jù)額外的返回值,接下來(lái)將為大家詳細(xì)介紹下,感興趣的各位可以參考下哈
- sql 語(yǔ)句練習(xí)與答案
- 深入C++ string.find()函數(shù)的用法總結(jié)
- SQL Server中刪除重復(fù)數(shù)據(jù)的幾個(gè)方法
- sql刪除重復(fù)數(shù)據(jù)的詳細(xì)方法
- SQL SERVER 2000安裝教程圖文詳解
- 使用sql server management studio 2008 無(wú)法查看數(shù)據(jù)庫(kù),提示 無(wú)法為該請(qǐng)求檢索數(shù)據(jù) 錯(cuò)誤916解決方法
- SQLServer日志清空語(yǔ)句(sql2000,sql2005,sql2008)
- Sql Server 2008完全卸載方法(其他版本類似)
- sql server 2008 不允許保存更改,您所做的更改要求刪除并重新創(chuàng)建以下表
- SQL Server 2008 清空刪除日志文件(瞬間日志變幾M)
- Win7系統(tǒng)安裝MySQL5.5.21圖解教程
- 將DataTable作為存儲(chǔ)過(guò)程參數(shù)的用法實(shí)例詳解
Mssql數(shù)據(jù)庫(kù)教程Rss訂閱編程教程搜索
Mssql數(shù)據(jù)庫(kù)教程推薦
- SQL2005利用ROW_NUMBER() OVER實(shí)現(xiàn)分頁(yè)功能
- sql server多行數(shù)據(jù)拼接的實(shí)例方法
- SQL 2005 ERROR:3145 解決辦法(備份集中的數(shù)據(jù)庫(kù)備份與現(xiàn)有的數(shù)據(jù)庫(kù)不同)
- 謹(jǐn)記SQL Server索引管理的六大鐵律
- 真正高效的SQLSERVER分頁(yè)查詢(多種方案)
- 如何使用SQL Server代理運(yùn)行CmdExec命令
- sql server 2008 不允許保存更改,您所做的更改要求刪除并重新創(chuàng)建以下表
- 通過(guò)SQL繪制楊輝三角的實(shí)現(xiàn)方法介紹
- SQLServer日志清空語(yǔ)句(sql2000,sql2005,sql2008)
- SELECT 賦值與ORDER BY沖突的問(wèn)題
猜你也喜歡看這些
- mysql重裝后出現(xiàn)亂碼設(shè)置為utf8可解決
- MySQL如何設(shè)置密碼
- 網(wǎng)頁(yè)模板MySQL出現(xiàn)Can't create/write to file 'C:\Windows\TEMP\#sql_990_0.MYI解決辦法
- MySQL筆記之?dāng)?shù)學(xué)函數(shù)詳解
- SQL語(yǔ)句替換字段中的換行符,回車符
- 基于mysql事務(wù)、視圖、存儲(chǔ)過(guò)程、觸發(fā)器的應(yīng)用分析
- 深入mysql "ON DUPLICATE KEY UPDATE" 語(yǔ)法的分析
- MySQL索引簡(jiǎn)單分析
- 更新text字段時(shí)出現(xiàn)Row size too large報(bào)錯(cuò)應(yīng)付措施
- 遠(yuǎn)程連接mysql數(shù)據(jù)庫(kù)注意點(diǎn)記錄
- 相關(guān)鏈接:
- 教程說(shuō)明:
Mssql數(shù)據(jù)庫(kù)教程-在SQL Server中實(shí)現(xiàn)最短路徑搜索的解決方法
。