如何在Flightroutes-Table中获取最短路径

时间:2017-04-19 11:22:37

标签: sql oracle shortest-path

我无法获得声明以便在航班上停止所有中途停留。

我的桌子上有flightroutes,如下所示,它有一个源机场和一个目的地机场。 现在我想从机场A到机场B获得最短的飞行时间(最少的中途停留),没有从A到B的直接路线所以我必须将几条路线连接在一起。

所以例如,如果我想从18到1403我想要获得路线

(18 > 24 | 24 > 87 | 87 > 1403) 

而不是

(18 > 24 | 24 > 87 | 87 > 99| 99 > 1403)

以下是一些测试数据

src_apid | dst_apid
---------+----------
18       | 24
24       | 87
87       | 99
87       | 1403
99       | 18
99       | 1403

我的尝试看起来像这样:

WITH rejkabrest (
src_apid,
dst_apid
) AS (
SELECT
    src_apid,
    dst_apid
FROM
    routes
WHERE
    src_apid = 18  
UNION ALL 
SELECT
    a.src_apid,
    a.dst_apid
FROM
    routes a
    INNER JOIN rejkabrest b ON a.src_apid = b.dst_apid
    WHERE b.dst_apid = 1403
) SELECT
src_apid, dst_apid
FROM
rejkabrest;

但这样我才能获得所有从源头18机场开始的路线。如果我尝试另一种方式,我会遇到循环问题。

希望你们能帮助我。非常感谢提前!

3 个答案:

答案 0 :(得分:3)

使用connect by nocycle和功能rank()

select path
  from (
    select r.*, rank() over (order by lvl) rnk
      from (select routes.*, level lvl, 
                   sys_connect_by_path(src_apid, '->')||'->'||dst_apid path
              from routes 
              connect by nocycle src_apid = prior dst_apid and src_apid <> 1403
              start with src_apid = 18) r
      where dst_apid = 1403 )
  where rnk = 1

演示:

with routes (src_apid, dst_apid ) as (
    select 18,   24 from dual union all
    select 24,   87 from dual union all
    select 87,   99 from dual union all
    select 87, 1403 from dual union all
    select 99,   18 from dual union all
    select 99, 1403 from dual )
select path
  from (
    select r.*, rank() over (order by lvl) rnk
      from (select routes.*, level lvl, 
                   sys_connect_by_path(src_apid, '->')||'->'||dst_apid path
              from routes 
              connect by nocycle src_apid = prior dst_apid and src_apid <> 1403
              start with src_apid = 18) r
      where dst_apid = 1403 )
  where rnk = 1

PATH
--------------------
->18->24->87->1403

答案 1 :(得分:3)

这是一种递归构建路径的方法。使用CYCLE子句可以避免循环异常。您可以使用Oracle KEEP FIRST从找到的路径获得最短路径。

with cte (dst_apid, path, stops) as
(
  select dst_apid, src_apid || ' > ' || dst_apid as path, 0 as stops
  from routes
  where src_apid = 18  
  union all 
  select r.dst_apid, cte.path || ' > ' || r.dst_apid, cte.stops + 1
  from cte 
  join routes r on r.src_apid = cte.dst_apid
  where cte.dst_apid <> 1403
)
cycle dst_apid set cycle to 1 default 0
select max(path) keep (dense_rank first order by stops)
from cte
where cte.dst_apid = 1403;

KEEP FIRST外,这是标准SQL。您可以使用SELECT path FROM cte WHERE dst_apid = 1403 FETCH FIRST 1 ROW ONLY代替此标准。从12c开始,Oracle支持此语法。

答案 2 :(得分:1)

如果你想要每次飞行一行,想到的唯一解决方案是两个递归查询。第一个建立航班路线,编号为1,1.1,1.2,1.1.1等;秒收集属于同一路线的航班。相当复杂:

with cte1 (routepart, pos, src_apid, dst_apid) as
(
    select to_char(rownum) as routepart, 1 as pos, src_apid, dst_apid
    from routes
    where src_apid = 18  
  union all 
  select cte1.routepart || '-' || rownum, pos + 1, r.src_apid, r.dst_apid
  from cte1 
  join routes r on r.src_apid = cte1.dst_apid
  where cte1.dst_apid <> 1403
)
cycle src_apid set cycle to 1 default 0
, cte2 (route, routepart, pos, src_apid, dst_apid) as
(
  select routepart as route, routepart, pos, src_apid, dst_apid
  from cte1
  where dst_apid = 1403
  union all
  select cte2.route, cte1.routepart, cte1.pos, cte1.src_apid, cte1.dst_apid
  from cte1
  join cte2 on cte2.routepart like cte1.routepart || '%'
            and nvl(length(regexp_replace(cte2.routepart, '[[:digit:]]', '')), 0) =
                nvl(length(regexp_replace(cte1.routepart, '[[:digit:]]', '')), 0) + 1
)
cycle src_apid set cycle to 1 default 0
select pos, src_apid, dst_apid
from
(
  select
    cte2.*, 
    rank() over (order by length(regexp_replace(route, '[[:digit:]]', ''))) as rn
  from cte2
)
where rn = 1
order by route, pos;

如果您不想要关系,请使用ROW_NUMBER代替RANK